the2g

Next.jsブログの最適化

目次
Next.js

ブログをある程度最適化したので、その際のショートメモ。前回の続編のようなものです。記事にするほどでもないのですが忘却録として。以下のパッケージを利用してSSGしています。

  • Next.js v10.0.5
  • Contentful v8.1.6
  • Theme UI v0.4.0-rc.14

Theme UI

スタイル方法を混合させない

Theme UIとstyled-jsxを混合させると、バッティングしない部分でも影響が出る模様。styled-jsxでbodyのword-breakを定義していたのですが、本番環境のページリロード前後で結果が異なることがあった。Bodyへのグローバルスタイルは、素直にthemeのstyles.rootに定義するのが良いようです。

passHref

next/linkにはpassHrefを割り当てる。Theme UIでこれをつけない場合、アンカー要素のhrefが取り除かれ、SEOでも影響を受けます。これはEmotionなどjsx pragmaを利用するライブラリのソリューションとしてドキュメントに記載されています。Theme UIはEmotionをベースにしたミニフレームのため、これに従います。

page props

Next.js v9ではprefetch時のレスポンスにHTMLが乗っていましたが、 v10ではページpropsが単独JSONで乗っています。また、動的ルートのchunkも別途取得します。以下は公式チュートリアルのサンプルをv9とv10でコンパイルした比較です。

ページ遷移は高速になりますが、多くのページ内リンクがある場合はJSONの量も増えます。そこでprefetch={false}を設定し、滅多に読み込まれないリンクは事前ロードをさせないようにするのが良いようです。この場合、リンクにマウスカーソルが乗ったときにpropsとなるJSONがロードされます。

重たいコンポーネント

ボトルネックになるのがコードハイライト。Theme UIには@theme-ui/prismも用意されていますが、自分はカスタマイズ目的でreact-syntax-highlighterを利用しています。jsxをサポートするにはPrismを使う必要があるのですが、これはサイズが相当大きい。そこでハイライトが必要な言語セットだけを読み込むようにしました。

import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
import nord from "react-syntax-highlighter/dist/esm/styles/prism/nord";

// サポートしたい言語
import jsxx from "react-syntax-highlighter/dist/esm/languages/prism/jsx";
import javascript from "react-syntax-highlighter/dist/esm/languages/prism/javascript";
import json from "react-syntax-highlighter/dist/esm/languages/prism/json";
import php from "react-syntax-highlighter/dist/esm/languages/prism/php";
import css from "react-syntax-highlighter/dist/esm/languages/prism/css";
import bash from "react-syntax-highlighter/dist/esm/languages/prism/bash";
import markdown from "react-syntax-highlighter/dist/esm/languages/prism/markdown";

// 必要な言語を登録
SyntaxHighlighter.registerLanguage("jsx", jsxx);
SyntaxHighlighter.registerLanguage("javascript", javascript);
SyntaxHighlighter.registerLanguage("json", json);
SyntaxHighlighter.registerLanguage("php", php);
SyntaxHighlighter.registerLanguage("css", css);
SyntaxHighlighter.registerLanguage("bash", bash);
SyntaxHighlighter.registerLanguage("markdown", markdown);

// コードブロック
const CodeBlock = ({ language, value }) => {
  return (
    <SyntaxHighlighter language={language} style={nord}>
      {value}
    </SyntaxHighlighter>
  );
};

Next.js 10ではdist/esm以下のファイルを利用しようとするとビルドエラーが起こるため、トランスパイラの設定が必要です。next-transpile-modulesをインストールし、以下のようなファイルを配置します。

// next.config.js
const withTM = require("next-transpile-modules")(["react-syntax-highlighter"]);
module.exports = withTM();

一先ず完了ですが、ページをリロードすると強調が狂う・本番環境で動作しない等の不具合が発生することがあります。hydrationが原因だそうです。

この場合、PrismLightではなくPrismAsyncLightを使うのが手っ取り早い。こちらはcodeを先にレンダリングし、コード内で必要とされる言語ファイルを判断して必要分だけ非同期でロードしてくれます。サイズは若干増えますが、レンダリングブロック時間を減らすこともできます。

import { PrismAsyncLight as SyntaxHighlighter } from "react-syntax-highlighter";

また、Next.jsのnext/dynamicはSSRでなくともssl:falseを渡すと、動的にコンポーネントをインポートすることもできます。

画像の遅延ロード

next/imageで画像を遅延読み込みすることがきますが、遅延だけが目的ならば現状はreact-lazy-loadの方が安定しているように思えます。fillを指定しない限り、高さや幅の指定が必須なのもやや扱いづらい。

以上。1記事のサイズ500KB前後・低速回線でも読み込み500ms〜1sに抑えられるようになりました。他にもいくつかありますが、このブログ固有な面もあるのでここまでにしておきます。

Share article