1. ホーム
  2. Next.js

【Next.js】generateStaticParams で動的ルートを静的生成(SSG)する方法

Share

Next.js の App Router で [slug] のような動的ルート(URLの一部が可変になるページ)を作ると、既定ではリクエストのたびにサーバーでページを組み立てます。しかし、ブログ記事のように内容が決まっているページなら、ビルド時にあらかじめHTMLを作っておく方が高速で、サーバーの負荷も下がります。この「事前生成」を動的ルートで行うための関数が generateStaticParams です。この記事では、SSG(Static Site Generation=静的サイト生成)の考え方から、generateStaticParams の基本、実際のデータからパスを作る方法、複数セグメントの扱い、dynamicParams による挙動の制御まで、初心者〜中級者向けに解説します。

なぜ動的ルートを静的生成するのか

SSG(静的サイト生成)とは、ページのHTMLをビルドのタイミングで一度だけ作っておき、あとはその出来上がったファイルを配信する方式のことです。アクセスのたびにサーバーで計算しないため表示が速く、CDN(世界中に配置された配信サーバー)にも載せやすいという利点があります。

問題は、app/blog/[slug]/page.tsx のような動的ルートです。[slug] の部分は hello-world にも nextjs-tips にもなり得るため、Next.js は「どのURLを事前に用意すればよいか」を自動では判断できません。そこで、存在するパスの一覧を Next.js に教える役割を果たすのが generateStaticParams です。この関数が返したパスの分だけ、ビルド時に静的HTMLが生成されます。

generateStaticParams の基本

generateStaticParams は、動的ルートの page.tsx と同じファイルに export します。返すのは、動的セグメントの名前をキーに持つオブジェクトの配列です。[slug] というフォルダなら、キーは slug になります。

app/blog/[slug]/page.tsx
// 事前生成したいパスの一覧を返す
export function generateStaticParams() {
  return [{ slug: "hello-world" }, { slug: "nextjs-tips" }];
}

export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  // params は Promise なので await して取り出す(Next.js 15 以降)
  const { slug } = await params;
  return <h1>{slug}</h1>;
}

この例では { slug: "hello-world" }{ slug: "nextjs-tips" } の2件を返しているので、ビルド時に /blog/hello-world/blog/nextjs-tips の2ページが静的HTMLとして生成されます。配列の各要素がそのまま1つのURLに対応する、と考えると分かりやすいです。なお Next.js 15 以降では page が受け取る paramsPromise になったため、await で中身を取り出しています。

記事一覧のslugをデータから取得して返す

実際のブログでは、パスをコードに直書きするのではなく、APIやファイルから記事の一覧を取ってきて生成します。generateStaticParamsasync にできるので、関数の中で await を使ってデータを取得できます。

app/blog/[slug]/page.tsx
type Post = { slug: string; title: string };

export async function generateStaticParams() {
  // ビルド時に記事の一覧を取得する
  const res = await fetch("https://api.example.com/posts");
  const posts: Post[] = await res.json();

  // [{ slug: "..." }, ...] の形に変換して返す
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

ポイントは、返す配列の各オブジェクトが { slug: string } の形になっていることです。取得したデータをそのまま返すのではなく、mapセグメント名(ここでは slug)をキーにしたオブジェクトへ変換する点に注意してください。ローカルのMarkdownファイルからslugを集める場合も、最終的にこの形の配列を返せば同じように動きます。

複数の動的セグメントを扱う

app/[category]/[slug]/page.tsx のように、動的セグメントが2つ以上ある場合もあります。このときは、返すオブジェクトにすべてのセグメント名を含めますcategoryslug の両方をキーに持つオブジェクトを並べます。

app/[category]/[slug]/page.tsx
export function generateStaticParams() {
  // category と slug の組み合わせごとに1ページ生成される
  return [
    { category: "tech", slug: "nextjs" },
    { category: "tech", slug: "typescript" },
    { category: "life", slug: "diary" },
  ];
}

この例では、/tech/nextjs/tech/typescript/life/diary の3ページが生成されます。組み合わせ1つにつき1ページなので、カテゴリーと記事の対応関係をそのまま配列で表現するイメージです。返すオブジェクトに片方のキーが欠けているとエラーになるため、全セグメント分のキーを必ず揃えるようにしましょう。

事前生成していないパスへのアクセスを制御する dynamicParams

generateStaticParams が返さなかったパス、たとえば新しく追加された記事のURLにアクセスがあったとき、どう振る舞うかを決めるのが dynamicParams です。これは page.tsxboolean の値として export します。既定値は true です。

app/blog/[slug]/page.tsx
// 一覧に無いパスは 404 にする
export const dynamicParams = false;

export function generateStaticParams() {
  return [{ slug: "hello-world" }];
}

truefalse で、事前生成されていないパスへの挙動が次のように変わります。

dynamicParams一覧に無いパスにアクセスしたときの挙動
true(既定)リクエスト時にそのページを生成して表示する。以降はキャッシュされ、次回から静的配信される。
falseページを生成せず、404(Not Found)を返す。

記事が固定で、想定外のURLは存在させたくない場合は false にして 404 を返すのが安全です。一方、記事があとから増えるサイトでは、既定の true のままにしておけば、ビルド後に追加されたパスもアクセス時に生成されます。サイトの性質に合わせて選びましょう。

params や generateMetadata との関係

generateStaticParams が返したオブジェクトは、そのまま各ページの params として渡されます。{ slug: "hello-world" } を返せば、そのページの params{ slug: "hello-world" } になる、という対応です。この paramspage だけでなく、ページの <title> などを設定する generateMetadata 関数でも同じように受け取れます。

app/blog/[slug]/page.tsx
import type { Metadata } from "next";

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}): Promise<Metadata> {
  // generateStaticParams が返した slug を受け取れる
  const { slug } = await params;
  return { title: `記事: ${slug}` };
}

このように、generateStaticParams で「どのパスを作るか」を決め、generateMetadatapage がそれぞれの params を受け取ってページの中身とメタ情報を組み立てる、という流れになります。3つの関数が params を軸につながっていると理解しておくと、動的ルートの静的生成がぐっと分かりやすくなります。

まとめ

generateStaticParams は、[slug] のような動的ルートをビルド時に静的HTMLとして事前生成(SSG)するための関数です。動的ルートの page.tsxexport し、{ slug: "..." } のようにセグメント名をキーにしたオブジェクトの配列を返すと、その件数分のページが生成されます。実際にはAPIやファイルから記事一覧を async で取得し、map でこの形へ変換して返すのが定石です。[category]/[slug] のような複数セグメントでは全セグメント分のキーを揃え、事前生成していないパスへのアクセスは dynamicParamstruefalse で挙動を制御します。返した値はそのまま params として pagegenerateMetadata に渡ります。まずは小さなブログの記事ページで、静的生成を試してみてください。

参考ページ