Nextjs の App Route の Dynamic Route を利用してブログのページを作る(前編)

公開日 :

ブログの記事に関してそれぞれのページを用意したいと思います。前編では ID を利用して URL を作成する形で作っていきます。

ID を確認する

すでに動作しているコードを利用して、以下のようにページでの表示を切り替えます。

      <h1>Content Hub ONE - Title list</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            {post.id} - {post.title}
          </li>
        ))}
      </ul>

結果として、以下のような HTML を取得できています。

<main>
  <h1>Content Hub ONE - Title list</h1>
  <ul>
    <li>9CKkxq4hmEiNNIeveMJQ_w<!-- --> - <!-- -->test test</li>
    <li>zueShIf2qk6CzN5EL-mAIQ<!-- --> - <!-- -->Did you know Headless CMS?</li>
    <li>8J0Q3r0-H02ExS1sj27v8A<!-- --> - <!-- -->Welcome to Content Hub ONE Blog</li>
  </ul>
</main>

ここで取得できているのは Content Hub ONE のコンテンツに付与されている ID になります。今回はこの ID をパスに利用したいと思います。

Dynamic Routes を作成して動作確認

App Router で利用している Dynamic Routes を利用する際には、従来のファイルに id を記述する [slug].tsx のように記述するのではなく、パスとして app/[slug]/page.tsx という形でディレクトリを作成してそこにキーを設定する形になります。シンプルにサンプルをまず動かします。

export default function Page({ params }: { params: { slug: string } }) {
  return <div>My Post: {params.slug}</div>;
}

このファイルを作成した後、http://localhost/blog/hello とアクセスすると、以下のような結果が表示されています。

random desc

まずパスの記述方法が変わっていることがわかります。この形であれば URL で ID を取得することができますが、今回はブログの ID を利用するため次の手順を追加していきます。

ID を利用して作成する

パスを作成するにあたっては、generateStaticParams という関数を利用する形となります。まず以下のコードを実行してみます。

import getAllBlog from "@/api/queries/getBlog";

export async function generateStaticParams() {
  const posts = await getAllBlog();

  return posts.map((post) => ({
    slug: post.id,
  }));
}

export default function Page({ params }: { params: { slug: string } }) {
  const slug = params.slug;
  return <div>{slug}</div>;
}

この段階ではまだ全ての slug を利用して設定された path のキーを返します。ここで一度 npm run build を実行するとどうなるでしょうか?以下のような形で、3つの記事を表示する動きをしているのがわかります。

random desc

ID を取得できているので、この ID を利用してブログの記事を生成していきます。

ブログの記事を取得する

ブログの記事を取得するための ID がすでにわかっているため、その ID を利用して Query を投げる形となります。

query Blog {
  blog(id: "8J0Q3r0-H02ExS1sj27v8A") {
    id
    name
    title
    description
    publishDate
    blogImage {
      results {
        description
        fileHeight
        fileId
        fileName
        fileSize
        fileType
        fileUrl
        fileWidth
        id
        name
      }
    }
  }
}

この ID のところに generateStaticParams から渡された slug を利用して Query を投げたいと思います。interfaces/blog/index.ts のファイルに ID を利用してクエリを動かすコードを追加します。

export const getBlogByIdQuery = (id: string) => {
  return `query Blog {
      blog(id: "${id}") {
        id
        name
        title
        description
        publishDate
        blogImage {
          results {
            description
            fileHeight
            fileId
            fileName
            fileSize
            fileType
            fileUrl
            fileWidth
            id
            name
          }
        }
      }
    }
  `;
};

この Query をすでに作成している fetchGraphQL を利用して Slug の ID を利用してデータを取得するようにします。これに関しては、 api/queries/getBlog/index.ts に以下の関数を追加してください。

export async function getBlogBySlug(slug: string): Promise<Partial<Blog>> {
  const blogResponse: BlogResponse = (await fetchGraphQL(
    getBlogByIdQuery(slug)
  )) as BlogResponse;

  return blogResponse.data.blog;
}

以前作成をした getAllBlog は全てのブログ記事を取得しますが、今回は slug に指定された id を利用してブログの記事を返す形となります。

では app/blog/[slug]/page.tsx のファイルに戻って、Page の中を以下のように書き換えます。

export default async function Page({ params }: { params: { slug: string } }) {
  const post = await getBlogBySlug(params.slug);

  if (!post) {
    return notFound();
  }

  return <div>{post.title}</div>;
}

ここで突然 notFound(); を呼び出しています。これは next/navigation で準備されているので以下の1行を追加してください。

import { notFound } from "next/navigation";

これで ID を利用してブログのタイトルが表示されるようになります。

random desc

まとめ

ブログの個別のページに関して、ID を利用して URL を設定して表示することができました。ただ、これでは URL にキーワードも入っておらず意味がない、という感じになってしまいますので、次回はこの URL の部分をもう少し使い勝手の良い形にしたいと思います。