まくろぐ

Next.js のダイナミックルーティング機能を利用する (getStaticPaths, getStaticProps, getServerSideProps)

更新:
作成:

ダイナミックルーティングとは

Next.js では、pages/books/[id].tsx のようなファイル名でページを作成すると、1 つのファイルで、

  • /books/001
  • /books/002
  • /books/003

のようなパス (URL) によるアクセスをハンドルできます。 これを ダイナミックルーティング (Dynamic Routes) 機能と呼びます。

ダイナミックルーティングの実装(SSG の場合)

静的ジェネレーション (SSG: Static Generation)、つまり Web サイトのビルド時にすべての HTML ファイルを生成してしまうには、あらかじめどのようなパラメーター(上記の例では id)でのアクセスが行われるかを把握した上で、各ページの内容を生成する必要があります。 これを実現するには、ページコンポーネントの実装ファイル (pages/*.tsx) で、次のような async 関数を実装して export します。

getStaticPaths 関数
URL のパラメーター部分(上記の例では id)で指定可能な値を返すように実装します。言い換えると、プリビルドすべきページの一覧情報を Next.js に教えてあげるための実装です。この関数は通常、Web サイトのビルド時にだけ実行されますが、開発サーバー (next dev) 使用時はアクセス毎に呼び出されることに注意してください。
getStaticProps 関数
指定されたパラメーターに対応するデータを返すように実装します。この値がページコンポーネントに引数として渡されます。この関数は通常、Web サイトのビルド時にだけ実行されますが、getStaticPath の戻り値のプロパティ fallbacktrue、あるいは 'blocking' に設定した場合は、アクセス時に呼び出される可能性があります(後述のフォールバックの説明を参照)。

次の例では、/books/001/books/002/books/003 といった URL でアクセス可能な books/[id] ページを定義しています。

pages/books/[id].tsx
import Head from 'next/head'
import React from 'react'
import { GetStaticPaths, GetStaticProps } from 'next'

// パスの構成要素を表す型 (/books/[id].tsx なら id)
type Params = {
  id: string;
}

// ページコンポーネントに渡されるオブジェクトの型
type Book = {
  title: string;
  author: string;
}

// どのようなパラメーター(パス)でアクセスできるかを定義します。
export const getStaticPaths: GetStaticPaths<Params> = async () => {
  // /books/001、/books/002、/books/003 のページを事前生成するには、
  // 次のように paths プロパティの値を設定して返します。
  // 本来は id のリストを外部 API(getBookList など)で取得します。
  return {
    paths: [
      { params: { id: '001' } },
      { params: { id: '002' } },
      { params: { id: '003' } }
    ],
    fallback: false  // 上記以外の id が指定された場合に 404 ページを返す
  }
}

// context.params プロパティに URL のパラメーター部分の値を参照できるので、
// その値に応じたコンテンツを返すように実装します。
export const getStaticProps: GetStaticProps<Book> = async context => {
  // ファイル名が [id].tsx なので id パラメーターを取り出すことができる
  const { id } = context.params as Params

  // 本来はここで getBook(id) のように API を呼び出してデータを取得する
  const book = {
    title: `Title-${id}`,
    author: `Author-${id}`
  }

  // ページコンポーネントに渡す Book オブジェクトを props プロパティに設定する
  return { props: book }
}

// ページコンポーネントの実装
const BookPage: React.FC<Book> = (book) => {
  return <>
    <Head>
      <title>{book.title} の詳細ページ</title>
    </Head>
    <ul>
      <li>タイトル: {book.title}</li>
      <li>著者: {book.author}</li>
    </ul>
  </>
}
export default BookPage

フォールバック制御

fallback プロパティ

上記の例では、getStaticPaths 関数の戻り値の fallback プロパティに false をセットしていますが、この値は、想定外のパラメーターを指定された場合のフォールバック方法 を設定するために使用します。

fallback: false
paths プロパティで指定したパラメーター以外でのアクセス時に、404 ページ (pages/404.tsx) を返します。
fallback: true
paths プロパティで指定したパラメーター以外でのアクセス時に、サーバーサイドで getStaticProps を呼び出して動的にページを生成します。それ以降のアクセスは、そのページを返します。この機能を利用する場合は、Next.js サーバーで Web サイトをホスティングしている必要があります(next export で HTML エクスポートした場合は動作しません)。サーバーサイドでのページ生成中は、Web ブラウザ側には瞬間的に「フォールバックページ」が表示されることになります。フォールバック表示中かどうかを判断するには、useRouter フックを利用して isFallback 情報を参照します(後述)。
fallback: 'blocking'
true を指定した場合とほぼ同じですが、サーバーサイドでの HTML 生成が終わってから初めて Web ブラウザにレスポンスが返されるところが異なります(なのでブロッキングという名前になっています)。true を指定した場合は、フォールバック表示(Loading 表示など)を行うことができますが、'blocking' を指定した場合は、フォールバック表示は行えません(実装は楽ですがユーザーへのレスポンスが遅くなります)。

getStaticProps でデータを生成できないときの処理

このフォールバック機能を有効にしているとき(fallback: true あるいは fallback: 'blocking' のとき)、getStaticProps は Web サイトへのアクセス時に、未知のパラメーター(上記の例の場合は id の値)を伴って呼び出される可能性があります。 それに対応するオブジェクト (Book) を見つけられない場合は、次のいずれかの対応を行う必要があります。

  • データが空であることを示すオブジェクト(空オブジェクトとか)を返して、ページコンポーネント内で適切にハンドルする
  • 404 コード(と 404 ページ)を返す({ notFound: true } を返す)
  • 別のパスにリダイレクトする({ redirect: ... } を返す)

例えば、次の例では、対応する Book オブジェクトを見つけられない場合に、空のオブジェクト {} を返すように実装しています。

export const getStaticProps: GetStaticProps<Book | {}> = async context => {
  // ... context.params.id に対応する Book を見つけられなかった場合
  return { props: {} }
}

const BookPage: React.FC<Book> = (book) => {
  if (book.title == undefined) {
    return <div>NOT FOUND</div>
  }
  // ...
}

潔く、404 ページを表示するのであれば、getStaticProps 関数の戻り値として { notFound: true } を返すだけで OK です。

export const getStaticProps: GetStaticProps<Book> = async context => {
  // ... Book を見つけられなかった場合
  return { notFound: true }
}
☝️ ワンポイント getStaticPaths の戻り値で fallback: false と設定している場合は、上記のような分岐処理は必要ありません。 パラメーターとして未知の値が指定された場合に、自動的に 404 が返されます(getStaticProps が呼び出されることがありません)。

不正なパラメーターを指定された時に別の URL へリダイレクトしてしまいたい場合は、戻り値で redirect プロパティを指定します。

return {
  redirect: { destination: '/', permanent: false }
}

フォールバック時のキャッシュの有効時間

フォールバックを有効にしているとき (fallback: true)、未知のパラメーターでページアクセスがあると、Next.js サーバー側で getStaticProps 関数が呼び出されて、動的にページ生成が行われます。 このとき、次回以降のアクセスのために、生成されたページはキャッシュされるわけですが、このキャッシュの有効期間は getStaticProps の戻り値の revalidate プロパティで秒単位で設定することができます。

export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: { posts },
    revalidate: 60 * 10  // 10 分間キャッシュ
  }
}

指定された秒数を経過後に、同じ URL にアクセスがあったときは、再度 getStaticProps が呼び出されて、最新データでページが再構築されます。 逆に、revalidate プロパティを設定しなかった場合は、最初に生成されたページがずーっと使われることになります。 revalidate プロパティを指定してサーバーサイドで定期的にページを再構築することを、Next.js では ISR: Incremental Static Regeneration と呼んでいます。

フォールバック時の一時表示(フォールバックページ)

フォールバック有効時(fallback: true のとき)、サーバーサイドで動的なページ生成が行われている最中(getStaticProps 実行中)は、クライアント側には「フォールバックページ」が表示されることになります。 フォールバックページでは、パラメーターとして渡される props は空っぽになるので、代わりに適切な Loading 表示などを行う必要があります。 現在のページがフォールバック中であるかどうかを調べるには、useRouter フックを使って、isFallback の値をチェックします。

import { useRouter } from 'next/router'

// ページコンポーネントの実装
const BooksPage: React.FC<Book> = (book) => {
  const router = useRouter()
  if (router.isFallback) {
    return <div>Loading...</div>
  }

  // ...
}

ダイナミックルーティングの実装(SSR の場合)

すべてのページを Web サーバーへのアクセス時に動的に生成することを、サーバーサイドレンダリング (SSR: Server-side Rendering) と呼びます。 ダイナミックルーティング機能を SSR で使用するには、getStaticPaths 関数や getStaticProps 関数の代わりに次の関数を実装します。

getServerSideProps 関数
Web サーバー(Next.js サーバー)へのアクセス時に呼び出されます。context.params を参照すると URL で指定されたパラメーター (id) を取得できるので、それに対応するデータを返すように実装します。このデータはページコンポーネントの引数として渡されます。

実装方法は getStaticProps と同様です。

pages/books/[id].tsx(抜粋)
export const getServerSideProps: GetServerSideProps<Book> = async context => {
  const { id } = context.params as Params

  // 本来はここで getBook(id) のように API を呼び出してデータを取得する
  const book = {
    title: `Title-${id}`,
    author: `Author-${id}`
  }

  return { props: book }
}

この関数は Web サーバーへのアクセス時に呼び出されるため、必ず Web サイトを Next.js サーバーでホスティングする必要があります(next export で静的な HTML ファイルとしてエクスポートした場合は動作しません)。 Next.js アプリのホスティングサービスとしては、Vercel が有名です。

SSR の仕組み (getServerSideProps) を使うと、Web ページへのアクセス時に毎回ページ生成が行われるため、クライアントへのレスポンスはどうしても遅くなります。 常に最新の情報を使ってページ生成を行う必要がないのであれば、できるだけ SSG: Static Generation によるレンダリングを行うべきです(getServerSideProps ではなく getStaticProps を実装する)。 getStaticProps でも、戻り値の revalidate プロパティを設定すれば、定期的にページを再構築できます (ISR)。

関連記事

まくろぐ
サイトマップまくへのメッセージ