PR

【Next.jsブログ構築】個別記事の URL を自動生成する動的ルーティングの設定方法

女性が柔らかい雰囲気でノートパソコンを操作しているイラスト。パソコンの画面からは、複数の Markdown ファイルが自動的に個別の Web ページへと生成・リンクされていくようなイメージが英語のテキスト(Dynamic Routing, Next.js)とともに表現されている。適度な色数でシンプルにまとまっている画像。 VPS・RentalServer
この記事は約11分で読めます。
記事内に広告が含まれています。
スポンサーリンク

これまでの Next.js ブログサイト構築シリーズをまとめたページは以下の通りです。

前回の第6回では、フォルダ内にある複数の Markdown ファイルを読み込み、
トップページに新着記事の一覧としてリスト表示させる仕組みを作りました。

しかし今の状態では、一覧に並んだ記事のタイトルをクリックしても、
個別の記事本文を読むことはできません。
本格的なブログとして機能させるためには、
タイトルをクリックした際に、
それぞれの記事専用のページが開く仕組みが必要です。

この記事では、
Next.js の非常に強力な機能である「動的ルーティング(Dynamic Routes)」を使って、
個別記事の URL(例:/posts/test-1)を自動生成し、
記事の本文を表示させる手順を解説します。

記事の前提条件と対象読者

この記事は、以下の環境と進捗を前提として手順を解説しています。

  • Next.js のバージョン: Next.js 16.2.1(React 19)環境の App Router を使用しています。
  • 対象読者: シリーズ第6回までを完了し、すでに posts フォルダに Markdown 記事が保存されており、トップページに新着記事の一覧が表示できている方を対象としています。

Next.js を構成するフォルダとファイルの役割

実際の作業に入る前に、これから編集する Next.js の各フォルダやファイルが、どのような役割を持っているのかを整理しておきます。サイトの仕組みを「劇団」に例えると分かりやすくなります。

  • posts フォルダ(倉庫):
    Markdown ファイル(記事の原稿データ)を保管しておくための単なるデータ置き場です。
  • lib フォルダ(裏方):
    画面には直接出ない、データ取得や計算などの「裏方プログラム」をまとめておく場所です。
  • app フォルダ(表舞台):
    読者が見る URL と画面を作る、 Next.js の最重要フォルダです。この中のフォルダ構成が、そのまま Web サイトの URL になります。
  • page.tsx (実際の画面):
    app フォルダ内に配置され、その URL にアクセスしたときに表示される「実際の画面(UI)のデザイン」を作るファイルです。

この役割分担を踏まえて、裏方に仕事を増やし、表舞台に型枠を作っていくのが今回の作業の流れです。

記事本文データを取得する裏方プログラムの追加

まずは、前回作成したデータ処理専用の裏方プログラム(lib/posts.ts)に、「指定されたファイル名の Markdown から、タイトルと本文を取り出してくる」という新しい仕事を追加します。

Cursor を使って lib/posts.ts を開き、現在書かれているプログラムの一番下(最終行のさらに下)に、以下のコードを追記して保存します。

// ▼▼▼ ここから下を追加 ▼▼▼

// 指定された ID(ファイル名)の記事データを取得する関数
export function getPostData(id: string) {
  // 1. 読み込むファイルの正しい場所(パス)を作る
  const fullPath = path.join(postsDirectory, `${id}.md`);
  
  // 2. ファイルの中身を読み込む
  // ※ fs によるファイルの読み込み処理は、サーバー側でのみ動作します。
  const fileContents = fs.readFileSync(fullPath, 'utf8');

  // 3. gray-matter を使って、フロントマター(タイトルや日付など)と本文を切り分ける
  const matterResult = matter(fileContents);

  // 4. React でエラーにならないよう、Date オブジェクトを文字列(YYYY-MM-DD)に変換する
  let dateString = matterResult.data.date;
  if (dateString instanceof Date) {
    dateString = dateString.toISOString().slice(0, 10);
  } else {
    dateString = String(dateString);
  }

  // 5. 取得したデータをまとめて返す
  return {
    id,
    content: matterResult.content, // マークダウンの本文
    title: matterResult.data.title as string,
    date: dateString,
  };
}

このコードにより、例えばシステムから test-1 と指示されたら、データ倉庫から test-1.md を探し出して中身を読み取ることができるようになります。

個別記事の「型枠」となるページを作成する

次に、実際の画面(表舞台)となるページを作成します。 Next.js では、フォルダ名を [id] のようにカッコ [] で囲むと、「ここはどんな URL が来ても受け付けます」という動的な型枠になります。

  1. Cursor の左側のファイル一覧で、 app フォルダの中に posts フォルダを作成します。
  2. その posts フォルダの中に、半角カッコを含めた [id] という名前のフォルダを作成します。
  3. その [id] フォルダの中に、 page.tsx というファイルを作成します。

ここまでの作業で、ブログのフォルダ構造は以下のようになります。

example-blog/
├── app/
│   ├── page.tsx
│   └── posts/
│       └── [id]/
│           └── page.tsx  ← 今回新しく作成した「型枠」ファイル
├── lib/
│   └── posts.ts
└── posts/
    ├── test-1.md
    └── test-2.md
  1. 作成した app/posts/[id]/page.tsx に、以下のプログラムを貼り付けて保存します。
import { getPostData } from '../../../lib/posts';
import ReactMarkdown from 'react-markdown';

// Next.js 15 以降、動的ルートの params は Promise として渡されるため
// コンポーネントを async 関数にして await で受け取る必要があります。
export default async function Post({
  params,
}: {
  // params は { id: string } を含む Promise 型として受け取る
  params: Promise<{ id: string }>;
}) {
  // 1. Promise を解決して、URL の [id](例: "test-1")を取り出す
  const { id } = await params;

  // 2. id に対応するマークダウンファイルを読み込み、記事データを取得する
  const postData = getPostData(id);

  // 3. 取得したタイトル、日付、本文を画面に表示する
  return (
    <main className="max-w-3xl mx-auto p-8">
      {/* タイトルを大きく太字で表示 */}
      <h1 className="text-3xl font-bold mb-2">{postData.title}</h1>

      {/* 日付をグレーのテキストで表示 */}
      <div className="text-gray-400 mb-8">{postData.date}</div>

      {/* マークダウン本文を prose スタイル(読みやすい文章レイアウト)で表示 */}
      {/* prose-invert はダークテーマ向けの配色、max-w-none は幅制限を解除 */}
      <div className="prose prose-invert max-w-none">
        <ReactMarkdown>{postData.content}</ReactMarkdown>
      </div>
    </main>
  );
}

Next.js 15 以降の最新仕様では、 URL のパラメータ(今回の [id] の部分)を非同期(Promise)で受け取るルールに変更されています。そのため、コード内で asyncawait を使って安全に処理を行っています。

トップページから個別記事へリンクを繋ぐ

型枠の準備ができたので、最後にトップページ(新着記事一覧)のタイトルをクリックして、個別記事へジャンプできるようにリンクを設定します。

  1. Cursor で、トップページのファイル(app/page.tsx)を開きます。
  2. 中身をすべて消去し、以下のプログラムにまるごと書き換えて保存します。
import { getSortedPostsData } from '../lib/posts';
// ★追加:Next.js 専用の高速なページ遷移を行うための Link コンポーネントを読み込む
import Link from 'next/link'; 

export default function Home() {
  // 1. 裏方プログラム(lib/posts.ts)を呼び出して、全記事のデータを取得し、日付の新しい順に並べたリストを受け取る
  const allPostsData = getSortedPostsData();

  // 2. 取得したデータをリスト状(カード型)に並べて画面に表示する
  return (
    <main className="max-w-3xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">新着記事一覧</h1>
      
      <ul className="space-y-4">
        {/* allPostsData の中身(id, date, title)を1つずつ順番に取り出して表示する */}
        {allPostsData.map(({ id, date, title }) => (
          // リストの各項目(カード)。
          // マウスが乗った時に背景色が変わるデザイン(hover:bg-gray-700 transition-colors)を追加しています。
          <li key={id} className="border border-gray-700 rounded-lg shadow-sm bg-gray-800 hover:bg-gray-700 transition-colors">
            
            {/* 通常の <a> タグではなく Next.js の <Link> コンポーネントを使用します。
              これにより、画面全体を再読み込みすることなく、一瞬でページが切り替わります。
              リンク先の URL は「/posts/記事のID(ファイル名)」になります。
            */}
            <Link href={`/posts/${id}`} className="block p-4">
              {/* 日付の表示 */}
              <span className="text-gray-400 text-sm">{date}</span>
              {/* タイトルの表示 */}
              <h2 className="text-xl font-semibold text-white mt-1">
                {title}
              </h2>
            </Link>

          </li>
        ))}
      </ul>
    </main>
  );
}

Next.js でページを移動する際は、通常の HTML の <a> タグではなく、専用の <Link> コンポーネントを使います。これにより、画面全体を読み込み直す通信が発生せず、アプリのように一瞬でページが切り替わるようになります。

動作確認(Next.js の起動とページ遷移)

すべての準備が整いました。設定が正しく反映されているか、ブラウザで確認してみましょう。

開発作業の区切りで毎回システムを終了させている場合は、ブラウザを開く前にターミナルから Next.js を起動する必要があります。

  1. Cursor の下半分にあるターミナル(黒い画面)を開き、ブログのフォルダに移動してから Next.js を起動します。
cd ~/example-blog
npm run dev
  1. ターミナルに起動完了のメッセージが表示されたら、ブラウザでブログのトップページにアクセスします。

トップページにアクセスすると、新着記事一覧が並んでいます。それぞれの記事がカード状に表示されており、マウスのカーソルを合わせると背景色が少し明るいグレーにフワッと変わることが確認できるはずです。

そのままタイトルをクリックしてみましょう。画面全体が白く再読み込みされることなく、一瞬で個別記事のページ(例: /posts/test-1 )へ切り替わり、 Markdown の本文が表示されます。ブラウザの「戻る」ボタンを押せば、同じく一瞬でトップページに戻ることも可能です。

まとめ

今回は、 Next.js の「動的ルーティング」機能を使い、一覧ページから個別記事のページへリンクを繋いで本文を表示する仕組みを構築しました。

test-1test-2 という URL のページを一つずつ手作業で作るのではなく、 [id] の型枠を使って Next.js が自動的にページを生成してくれるという、モダンな Web フレームワークの強力さを実感できたかと思います。

これで、ブログの核となる「記事一覧」と「個別記事」の画面遷移が完成しました。次回は、サイト全体で共通して表示される「ヘッダー」と「フッター」を作成し、ブログ全体のレイアウトデザインを整えていく工程に進みます。

コメント

タイトルとURLをコピーしました