Next.jsブログを高速化する画像・フォント最適化Tips
表示速度はユーザー体験だけでなく、Googleの検索評価(Core Web Vitals)にも影響する要素です。このブログもNext.jsで構築する際、いくつかの基本的な最適化を最初から組み込んでいます。この記事では、実際に採用している設定を具体的に紹介します。
🛠 実装してみて分かったこと
パフォーマンス最適化というと、なんだか難しいことをたくさんやらないといけない印象があるかもしれません。でも実際に手を動かしてみると、効果が大きいものほど地味で、しかも最初にやっておけば以降ずっと効き続けるものが多いと感じました。逆に、後からまとめて最適化しようとすると「どこから手をつければいいか分からない」状態になりがちです。このブログでは、記事を1本も公開する前の段階で静的生成・フォント設定・画像コンポーネントの方針だけは先に決めておきました。結果として、記事を増やすたびに速度が悪化する心配をしなくて済んでいるのは、地味に助かっています。
実際にPageSpeed Insightsでこの記事のページを計測した結果です。

記事ページはできる限り静的生成(SSG)にする
このブログの記事ページは、generateStaticParamsを使ってビルド時にすべてHTMLとして生成しています。リクエストのたびにサーバー側で処理する必要がないため、表示速度が安定して速くなります。
export async function generateStaticParams() {
return getAllSlugs().map((slug) => ({ slug }));
}アフィリエイトブログのように「公開後は頻繁に内容が変わらない記事」は、基本的に静的生成が第一選択になります。
フォントはnext/fontでセルフホストする
Google FontsをそのままCDN経由で読み込むと、外部リクエストが発生し表示速度に影響します。Next.jsのnext/fontを使うと、ビルド時にフォントファイルを取得してNext.js自身がホスティングしてくれるため、外部リクエストなしで表示できます。
import { Geist } from "next/font/google";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});日本語フォントを使う場合はサブセット化(使用する文字だけに絞る)をしないとファイルサイズが大きくなりがちなので、必要な文字種だけに絞る設定も合わせて検討するとよいです。
実際にこのブログの記事ページを取得して確認すると、fonts.googleapis.comへの外部リクエストは無く、フォントファイル自体が自サイトの/_next/static/media/から配信されていることが分かります。
$ curl -s http://localhost:3000/blog/nextjs-performance-tips | grep -o 'fonts.googleapis.com\|fonts.gstatic.com'
(該当なし)
$ curl -s http://localhost:3000/blog/nextjs-performance-tips | grep -o 'static/media/[a-z0-9.-]*'
static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2
static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2画像はnext/imageを使い、サイズを明示する
このブログのサムネイル・アイキャッチはSVGで軽量に保っていますが、写真素材などラスター画像を使う場合はnext/imageコンポーネントを使うことで、自動的にWebP変換・レスポンシブサイズ配信・遅延読み込みが行われます。
import Image from "next/image";
<Image src="/images/example.jpg" alt="説明文" width={800} height={450} />widthとheightを明示することで、画像読み込み前にレイアウトのスペースが確保され、読み込み中にガタつく現象(レイアウトシフト)を防げます。これはCore Web Vitalsの「CLS」というスコアに直結します。
アイキャッチをSVGの幾何学イラストにした一番の理由も、実はここにあります。写真素材だとファイルサイズがどうしても大きくなりがちですが、SVGならベクターデータなので軽量なまま、しかもどんな解像度の画面でも綺麗に表示できます。デザインの好みだけでなく、表示速度の観点でも理にかなった選択だったと感じています。
見出し構造とmetadataをページごとに設定する
Next.jsのApp Routerでは、各ページでgenerateMetadata関数やmetadataオブジェクトを使ってタイトル・説明文・canonical URLなどを個別に設定できます。このブログでも記事ごとにタイトル・descriptionを出し分け、alternates.canonicalで正規URLを明示しています。
export async function generateMetadata({ params }) {
const { slug } = await params;
const post = await getPostBySlug(slug);
return {
title: post.title,
description: post.description,
alternates: { canonical: `https://example.com/blog/${slug}` },
};
}Next.js 15以降、paramsはPromiseになったため、params.slugと直接読まずawaitで解決してから使う必要があります(詳しくはサーバー/クライアントコンポーネントの記事で解説しています)。
構造化データ(JSON-LD)を埋め込む
記事ページにはArticleのJSON-LDと、パンくずリスト用のBreadcrumbListのJSON-LDを埋め込んでいます。これ自体が直接的に順位を上げるわけではありませんが、検索結果でパンくずが表示されるなど、リッチリザルトの対象になりやすくなります。
サイトマップ・robots.txtは自動生成にする
Next.jsにはsitemap.ts・robots.tsという規約があり、ファイルを置くだけでビルド時に/sitemap.xml・/robots.txtが自動生成されます。記事を追加するたびに手動でサイトマップを更新する必要がなく、更新漏れも防げます。
実際、記事を追加する作業を何度か繰り返してみて、この自動化のありがたみを実感しました。手動更新の運用だと「サイトマップの更新を忘れたまま記事を10本近く公開していた」ということが普通に起こり得ますが、ここを自動化しておくだけでその手のヒューマンエラーがまるごと消えます。
実際に/sitemap.xmlへブラウザで直接アクセスすると、記事を手動で追加していないのに全ページ分のURLが自動生成されています。

よくある質問
Q. 静的生成した記事を後から編集したらどうなりますか? A. Markdownファイルを編集して再ビルド・再デプロイすれば反映されます。CMSのような管理画面はありませんが、Gitで変更履歴を追える点はメリットです。
今のところの結論
静的生成・フォントのセルフホスト・画像最適化・metadata/構造化データの整備という基本を、記事を1本も公開する前に先に決めておいたのがこのブログでは効いています。派手な施策を後から積み増すより、この基本を最初に固めておく方が、記事を増やしても速度が悪化しないという安心感につながっています。
ホスティング先の選び方についてはこちらの記事でも解説しています。