🌻

【2021年3月更新】Next.js に Zero-runtime CSS in JS の linaria を導入する

2020/10/15に公開
1

Next.js10 系に linaria を導入する方法を解説します。

また、随時更新を可能にするためにリポジトリを公開しました。ソースを読めば分かる方はこちらをどうぞ!

linaria とは

Next.js については React の Web アプリケーション向けフレームワークということで知名度が高いと思いますが、 linaria についてはまだ知名度が比較的低いかもしれません。

特筆すべき特徴として、Write CSS in JS, but with zero runtime, CSS is extracted to CSS files during buildが挙げられており、CSS in JS の形式で実装できるものの、Webpack にてビルドした後は独立した CSS ファイルとして書き出されるとのことです。

また、linaria は代表的な CSS in JS ライブラリとして知られる styled-components と同様のインターフェースを採用しており、関連する VSCode 等のエディタ補完やシンタックスハイライトのメリットを享受できることが嬉しい点です。

今回は、Next.js に linaria を組み込み、yarn build、および yarn start した結果を見て本当にそのとおりに CSS ファイルとして独立しているのか、また、それらの CSS ファイルが preload されるのかを検証していきます。

前提条件

  • Next.js v10.0.9 で動作確認しています

導入手順

Next.jsのセットアップ

yarn create next-appコマンド等でセットアップしてください。

linaria のインストール

今回動作確認したバージョンは linaria@v2.1.0 です。

yarn add linaria@2.1.0

next-linaria のインストール

続いて、 next-linaria をインストールします。

yarn add next-linaria

next.config.js

続いて、next.config.js の編集です。無い場合は作成してください。

const withLinaria = require('next-linaria');

module.exports = withLinaria({
});

.babelrc

Babel も設定します。

{
  "presets": ["next/babel", "linaria/babel"]
}

@babel/coreのインストール

この状態で実行すると、@babel/core が無いという理由でエラーになるためインストールします。

yarn add -D @babel/core

以上で設定は終わりです。

実際に書いてみる

実際に以下のようなコンポーネントを書いてみました。

props として記事(article)を受け取って、そのサムネ画像を背景に、タイトルを表示するようなコンポーネントです。

import { styled } from "linaria/react";

const Card = styled.a<{ imageUrl: string }>`
  display: block;
  width: 200px;
  height: 140px;
  background-repeat: no-repeat;
  background-size: contain;
  background-image: ${(props) => `url('${props.imageUrl}')`};
`;

const ListItem = (props: Props) => {
  const href = `/articles/${props.article.id}`;
  return (
    <Link href={href} passHref>
      <Card imageUrl={props.article.imageUrl.url} title={props.article.title}>
        {props.article.title}
      </Card>
    </Link>
  );
};

ポイントは以下のとおりです。

  • styled.a<{ imageUrl: string }> のようにジェネリクスを使うことで、linaria コンポーネントに props の型定義を渡すことができます
  • Next.js 9.5.3 以降だと <Link href={href} passHref>のように書くことができます
  • background-image を指定するときに、URL の指定方法は上記のコードのようにurl()ごとクロージャに突っ込まないと CSS コンパイルが失敗しました。また、URL は仕様上半角のカッコを含むことができるので、シングルクォーテーションで URL を囲うのを忘れないでください

ここでは styled を使いましたが、css メソッドももちろん活用できます。余談ですが、styled を使うと HTML タグ名まで決定する責務を負ってしまうのが個人的には嫌なので、Props を受け取りたい場合以外は css を使おうと思っています。ドキュメントを読みましたが引数を型安全に受け取りたい場合は styled 一択のようです。

ビルド後の結果

以下のように、CSS ファイルが作成されただけでなく、preloadの設定も完璧に完了しています。割愛しますが、中身を見ると linaria で記載した CSS が、スペースや改行が圧縮された上で入っていました。

余談ですが、Nuxt.js ではビルド時に CSS を extract しても CSS に preload がされないバグが(依存している Webpack ライブラリの問題なのですが)ずっと残っており、この点はさすが Next.js、さすが linaria と感じました。

(2021年3月追記)上記のNuxt.jsの問題はv2.14.8以降で解消されています

注意点

_document.tsx を以下のように書いているとき、<Main>の外側のコンポーネントでは linaria を利用することができませんでした。具体的には、<Header>コンポーネント内で linaria を使っても CSS が当たりませんでした。

import Header from '../components/layouts/Header'

// ...

  render(): JSX.Element {
    return (
      <Html lang="ja">
        <Head />
        <body>
          <Header />
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }

そのため、Header.tsx では styled-jsx をやむなく利用しました。ここの原因については分かっていませんので、解消したり続報があれば追記します。

追記

2020/11/30

linaria で生成される CSS ですが、どうやら全部同じ CSS ファイルに出力されるようです。本当は、ページごとに利用している Component の CSS のみを出力するほうが Rules Count 的には望ましいと思います。中規模以上のサイトでは、この点がボトルネックになる可能性があり、改善の方法は要調査かもしれません。


もし記事が参考になれば、Zenn でサポートいただければ嬉しいです。
技術書・カンファレンス等に当てさせていただきます!


求人告知

私が CTO を務めている「オンライン家庭教師マナリンク」では、エンジニアを募集しております。

Web エンジニア及び、React Native エンジニアを探しております。

マナリンクでは、オンライン家庭教師の先生方のために、サイト上で自身のプロフィールを魅力的に発信できるようにしたり、オンライン指導専用アプリをリリースするなど、次々にプロダクトを開発しています。日々新しい技術を勉強して、試す機会を探している方にはうってつけな環境です。私も、半年間 Next.js を個人で勉強し続けた結果、新規メディア立ち上げの機会を得て早速 Next.js でリリースができました。

興味あれば Twitter に DM 等でご連絡をください!

Discussion