LoginSignup
0
1

More than 3 years have passed since last update.

Nextjs+TypescriptでHTMLをStatic Exportしたときにcssをinlineで出力する方法

Last updated at Posted at 2021-01-29

要約

  • 2021年1月現在、Nextjsでstatic exportするとcssがインライン( <style> タグにベタ書き)にならない。
  • cssを <link> タグでロードすると同期的にロードされてしまい、ページのレンダリングが遅くなるのでinlineにしたい(inlineだと非同期ロードなのでレンダリングまでの時間が早い)
  • そこで参考にしたgithubのissueにあるコードを元に(というかまんまコピー)_document.tsx を書いた。
  • この記事では一応何をやっているか自分のための備忘録として書いておく
  • 使っているNext.jsのバージョン: 10.0.5
import Document, { Head, Main, NextScript } from "next/document" 
import fs from "fs"
import path from "path"

declare type DocumentFiles = {
    sharedFiles: readonly string[];
    pageFiles: readonly string[];
    allFiles: readonly string[];
};

class InlineStylesHead extends Head {
    getCssLinks({ allFiles }: DocumentFiles) {
        const { assetPrefix } = this.context
        if (!allFiles || allFiles.length === 0) return null

        return allFiles
            .filter((file: any) => /\.css$/.test(file))
            .map((file: any) => (
                <style
                    key={file}
                    nonce={this.props.nonce}
                    data-href={`${assetPrefix}/_next/${file}`}
                    dangerouslySetInnerHTML={{
                        __html: fs.readFileSync(path.join(process.cwd(), '.next', file), 'utf-8'),
                    }}
                />
            ))
    }
}

export default class CustomDocument extends Document {
  render() {
    return (
      <html>
        <InlineStylesHead />
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

やっていること

NextJSの Head コンポーネントを継承した InlineStylesHead というのを定義している。NextJSの Head コンポーネントには getCssLinks という関数があり、ここでnextjsでビルドされたcssを呼び出すための <link> タグを生成している。 InlineStylesHead では同じようなことをしているが、 <style> タグでビルドされたcssを直接書いてしまっている。vercel/next.jsにある_document.tsxのコード をみると


getCssLinks(files: DocumentFiles): JSX.Element[] | null {
  //...長いので省略    
}

と書いてあるので、getCssLinksという関数には DocumentFiles という型の files という引数を渡される。が、 DocumentFiles という型はexportされていないので自身で定義している。定義自体はここにあるので

declare type DocumentFiles = {
    sharedFiles: readonly string[];
    pageFiles: readonly string[];
    allFiles: readonly string[];
};

_document.tsx で定義している。さて、これでビルドされたCSSのファイル一覧を取得できるようになったので、あとは Head クラス自体が持っているアセットのパスと結合し、ファイルを読み込んで <style> タグとして出力する。

getCssLinks({ allFiles }: DocumentFiles) {
    const { assetPrefix } = this.context
    if (!allFiles || allFiles.length === 0) return null

    return allFiles
        .filter((file: any) => /\.css$/.test(file))
        .map((file: any) => (
            <style
                key={file}
                nonce={this.props.nonce}
                data-href={`${assetPrefix}/_next/${file}`}
                dangerouslySetInnerHTML={{
                    __html: fs.readFileSync(path.join(process.cwd(), '.next', file), 'utf-8'),
                }}
            />
        ))
}

これを定義したコンポーネントを普段使う <Head> (これはNextJSのやつ) の代わりに使うとinlineで出力される。

まとめ

  • NextJSのHead コンポーネントではデフォルトでcssをinlineで出力しない
  • なので自分でそれっぽいコンポーネントを書く必要がある。
  • 参考issueにあったコードを元に作った

ほんとにinline化する必要ある?

  • Googleによれば「ちっちゃいcssならinlineでいいけど、大きいcssのときはinlineにしちゃだめ。スクロールしないと見えない範囲が大きくなっちゃうように見えてPageSpeed Insights的にだめだよ!大きいファイルは遅延ロードしてね!(意訳)」ということらしい。大きいファイルってどう判断すりゃいいんだ?
  • CSSの非同期ロードの方法はこの記事でも書いてあるとおり結構簡単に実装できるっぽいのでそっちのほうがいいのかな?
  • なんか尻すぼみな備忘録になっちゃった...
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1