Next.js 9.5系 × TSX に Zero-runtime CSS in JS の linaria を導入する

4 min読了の目安(約4100字TECH技術記事
Likes18

Next.js9.5 系に 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 v9.5.3 以上
  • TSX を利用している(コンポーネント等の拡張子が.tsx)

導入手順

Next.js のセットアップ

Next.js のセットアップ手順は任意です。記事執筆時点では 9.5.5 を利用しています。

私が作成した、Incremental Static Regeneration なサービスを立ち上げるためのテンプレートリポジトリがありますので、よろしければご活用ください。
https://github.com/TeXmeijin/nextjs-microcms-tsx-jamstack-aspida-boilerplate

本リポジトリは、microCMS、TSX 等について初期設定済みですが、CSS in JS についてはまだ対応していませんので、次節以降で linaria を導入していきます。

linaria のインストール

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

yarn add linaria@v2.0.0-rc.3

@zeit/next-css のインストール

続いて、 @zeit/next-css をインストールします。

yarn add @zeit/next-css

css-loader のセットアップをしてくれます。

next.config.js

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

const withCSS = require("@zeit/next-css");

module.exports = withCSS({
  webpack(config, options) {
    config.module.rules.push({
      test: /\.(js|tsx)$/,
      use: [
        {
          loader: "linaria/loader",
          options: {
            sourceMap: process.env.NODE_ENV !== "production",
          },
        },
      ],
    });

    return config;
  },
});

今回の記事の最重要ポイントはこちらです。

test: /\.(js|tsx)$/,

ここで、tsx を linaria/loader の対象に入れるのがポイントです。

.babelrc

Babel の設定も編集する必要があります。
最もシンプルなケースで、以下のように書く必要があります。

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

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

実際に書いてみる

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

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 と感じました。

注意点

_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 をやむなく利用しました。ここの原因については分かっていませんので、解消したり続報があれば追記します。