ゼロからの開発日誌

日々の学びなどを中心に

その5 Dynamic Routes

5-1. Dynamic Routes

  • 個々のブログページのURLはブログのデータに依存するようにしたいので、動的なルートを使用する
    • 任意のページのURLがデータに依存する場合は、dynamic routesを導入する

学べること

  • getStaticPathsを使用してdynamic routesでページを静的に生成する方法(5-3, 5-4)
  • ブログ投稿ごとにデータを取得するためのgetStaticPropsの書き方(5-5)
  • remarkを使用してマークダウンをレンダリングする方法(5-6)
  • 日付文字列をきれいに表示する方法(5-7)
  • 動的ルートを使用してページにリンクする方法(5-8)
  • 動的ルートに関するいくつかの有用な情報(5-9)

5-2. Steup

5-3. Page Path Depends on External Data

  • 前回のレッスンでは、ページのコンテンツが外部データに依存するケースを取り上げました。
  • インデックスページをレンダリングするために必要なデータをフェッチするために、getStaticPropsを使用しました。

  • このレッスンでは、各ページのパスが外部データに依存している場合について説明します。

  • Next.jsでは、外部データに依存するパスをもつページを静的に生成することができます。これによりNext.jsでは動的URLの利用が可能になります。

外部データに依存するページパス

  • 外部データ(データベースなど)に依存するページをプリレンダリングできる
  • 外部ページからidを引っ張り出して、/posts/[id]のようなページを生成する

ダイナミックルートでページを静的に生成する方法

  • ブログ投稿のための動的なルートを作成したい
    • 各投稿は/posts/<id>というパスを持ち、
    • <id>はトップレベルのpostsディレクトリの下にあるマークダウンファイルの名前とする
    • ssg-ssr.mdpre-rendering.mdがあるので、パスは/posts/ssg-ssr/posts/pre-renderingにしたい

ステップの概要

  • 以下のステップを踏むことで実現できます。まだ変更する必要はありません。次のページですべて行います。
  • まず、pages/postsの下に[id].jsというページを作成する(Next.jsでは、[で始まり]で終わるページがダイナミックルートになる)
  • pages/posts/[id].jsには、これまで作成した他のページと同じように、投稿ページをレンダリングするコードを記述します。
import Layout from '../../components/layout';

export default function Post() {
  return <Layout>...</Layout>;
}
  • さて、ここからが新機能です。
  • このページからgetStaticPathsという非同期関数をエクスポートします。
  • この関数では、idに対して取り得る値のリストを返す必要があります。
import Layout from '../../components/layout';

export default function Post() {
  return <Layout>...</Layout>;
}

export async function getStaticPaths() {
  // Return a list of possible value for id
}
  • 最後に、getStaticPropsを再び実装する必要があります
  • 今回は、与えられたidを持つブログ記事に必要なデータを取得します。
import Layout from '../../components/layout';

export default function Post() {
  return <Layout>...</Layout>;
}

export async function getStaticPaths() {
  // Return a list of possible value for id
}

export async function getStaticProps({ params }) {
  // Fetch necessary data for the blog post using params.id
}

[まとめ]

  • /posts/<id>というパスを持つページを静的に生成する(<id>はファイル名から決定され、これを動的と呼んでいる?)
    • 具体的には、/pages/posts/[id].jsというページを作り、下記の内容を含める([]がNext.jsにおいて動的ページであることを示している)
      • このページを描画するためのReact要素
      • idに基づく配列を返すgetStaticPaths関数
      • idを使用して、ブログの投稿データを取得するgetStaticProps関数

5-4. Implement getStaticPaths

getStaticPathsの実装

  • まず、ファイルを設定しましょう。
  • pages/postsディレクトリの中に[id].jsというファイルを作成します。
  • また、pages/postsディレクトリ内のfirst-post.jsを削除してください。これはもう使いません。
  • そして、pages/posts/[id].jsをエディタで開き、以下のコードを貼り付けます。後で...を記入します。
import Layout from '../../components/layout';

export default function Post() {
  return <Layout>...</Layout>;
}
  • 次に、lib/posts.jsを開き、以下のgetAllPostIds関数を一番下に追加します。
  • これは、postsディレクトリにあるファイル名(.mdを除く)のリストを返します。
export function getAllPostIds() {
  const fileNames = fs.readdirSync(postsDirectory);

  // Returns an array that looks like this:
  // [
  //   {
  //     params: {
  //       id: 'ssg-ssr'
  //     }
  //   },
  //   {
  //     params: {
  //       id: 'pre-rendering'
  //     }
  //   }
  // ]
  return fileNames.map((fileName) => {
    return {
      params: {
        id: fileName.replace(/\.md$/, ''),
      },
    };
  });
}

重要: 返されるリストは単なる文字列の配列ではなく、上のコメントのようなオブジェクトの配列である必要があります。 - 各オブジェクトはparamsキーを持ち、idキーを持つオブジェクトを含んでいなければなりません (ファイル名に[id]を使っているからです)。 - そうでないと、getStaticPathsは失敗します。

  • 最後に、getAllPostIds関数をインポートして、getStaticPathsの内部で使用することにします。
  • pages/posts/[id].jsを開き、エクスポートしたPostコンポーネントの上に以下のコードをコピーしてください。
import { getAllPostIds } from '../../lib/posts';

export async function getStaticPaths() {
  const paths = getAllPostIds();
  return {
    paths,
    fallback: false,
  };
}
  • paths は、getAllPostIds() が返す既知のパスの配列を含みます。
  • この配列には pages/posts/[id].js で定義されたパラメータが含まれます。詳しくはpathsキーのドキュメントをご覧ください。
  • fallback: false を無視する - 後で説明します。
  • これでほぼ完成です。しかし、まだgetStaticPropsを実装する必要があります。

次のページでそれをやってみましょう。

5-5. Implement getStaticProps

getStaticPropsの実装 - 指定されたidの投稿をレンダリングするために、必要なデータを取得する必要があります。 - そのためには、lib/posts.jsをもう一度開き、以下のgetPostData関数を一番下に追加します。これは、idに基づいた投稿データを返します。

export function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');

  // Use gray-matter to parse the post metadata section
  const matterResult = matter(fileContents);

  // Combine the data with the id
  return {
    id,
    ...matterResult.data,
  };
}
  • 次に、pages/posts/[id].jsを開き、この行を置き換えます。
import { getAllPostIds } from '../../lib/posts';

を以下のコードで実行します。

import { getAllPostIds, getPostData } from '../../lib/posts';

export async function getStaticProps({ params }) {
  const postData = getPostData(params.id);
  return {
    props: {
      postData,
    },
  };
}
  • 投稿ページでは、getStaticPropsのgetPostData関数を使って、投稿データを取得し、propsとして返すようになりました。
  • では、Postコンポーネントを更新してpostDataを使えるようにしましょう。
  • pages/posts/[id].jsで、エクスポートしたPostコンポーネントを以下のコードに置き換えてください。
export default function Post({ postData }) {
  return (
    <Layout>
      {postData.title}
      <br />
      {postData.id}
      <br />
      {postData.date}
    </Layout>
  );
}
素晴らしい!ダイナミックルートの生成に成功しました。

何か問題がありますか?
もしエラーに遭遇したら、ファイルに正しいコードがあることを確認してください。

pages/posts/[id].js はこのようになっているはずです。
lib/posts.jsはこのようになっているはずです。
(それでも動かない場合) 残りのコードは以下のようになっているはずです。
それでもうまくいかない場合は、GitHub Discussionsでコミュニティに質問してください。GitHub にコードをプッシュして、他の人が見られるようにリンクを張ってくれると助かります。

まとめ
繰り返しになりますが、私たちが行ったことをグラフィカルにまとめると、以下のようになります。

動的ルートを持つページを静的に生成する方法

まだ、ブログのマークダウンコンテンツを表示していません。次はこれをやってみましょう。



### 5-6. Render Markdown
### 5-7. Polishing the Post Page
### 5-8. Polishing the Index Page
### 5-9. Dynamic Routes Details