icatch
技術

Next.jsブログのパフォーマンススコアを87点→95点に改善した話

by Leon

自分のブログをLighthouseで測ったら87点だった。気になったので原因を調べて直したら95点になった。やったことをまとめておく。

何が問題だったか

Lighthouseのレポートを見ると、「レンダリングをブロックしているリクエスト」として89KBのCSSが指摘されていた。そのうち89.0KBが未使用という、ほぼ全部使われていないCSSが毎回読み込まれている状態だった。

before
before score

原因1: 日本語フォントのCSSが巨大だった

89KBのCSSの正体は、Noto Sans JPのフォント宣言だった。

Next.jsの `next/font/google` でNoto Sans JPを読み込むと、日本語の全unicode範囲に対応した `@font-face` 宣言が数百個生成される。それが277KBのCSSファイルになっていた。

自分の設定はこうなっていた。

const notoSansJP = Noto_Sans_JP({
  subsets: ["latin"],
  weight: ["400", "500", "700"],
  display: "swap",
});

weightを3種類指定していたため、フォント宣言が3倍あった。また `preload` のデフォルトが `true` なので、ブラウザがレンダリング前にこのCSSを必須で読み込んでいた。

修正はシンプルで、500を削除して `preload: false` を追加した。

const notoSansJP = Noto_Sans_JP({
  subsets: ["latin"],
  weight: ["400", "700"],
  display: "swap",
  preload: false,
});

これだけで277KB → 185KBになった。

原因2: シンタックスハイライトのCSSが全ページで読み込まれていた

ブログ記事のコードブロックにhighlight.jsを使っていて、CSSを `CodeBlock.tsx` にimportしていた。

// CodeBlock.tsx
import "highlight.js/styles/github-dark.css"; // ← これが問題

Next.jsでは、コンポーネントにimportしたCSSはメインバンドルに含まれる。そのため、コードブロックのないトップページやカテゴリページでも毎回読み込まれていた。

fix はCSSのimportをブログ記事ページ専用の `layout.tsx` に移動することだった。

// app/blog/[slug]/layout.tsx
import "highlight.js/styles/github-dark.css";

export default function BlogSlugLayout({ children }: { children: React.ReactNode }) {
  return <>{children}</>;
}

`CodeBlock.tsx` 側のimportは削除する。これでhighlight.jsのCSSはブログ記事ページにしか読み込まれなくなった。

結果

87点 → 95点に改善した。LCPも3.6秒 → 2.7秒にまで改善した。

after
after performance

まとめ

やったことは2つだけ。

・Noto Sans JPのweightを減らして `preload: false` にする
・highlight.jsのCSSを記事ページ専用のlayout.tsxに移動する

どちらもコード数行の修正で、LighthouseのCSSの「未使用」指摘を見ればすぐ気づける内容だった。Next.jsで日本語フォントを使っている人は一度確認してみるといいかもしれない。