TEXTBOOK SECTION / AI LEARNING

記事詳細ページ

Next.jsによるWebアプリケーション開発概論の「Next.jsとTailwind CSSで作る 岐阜県ローカルエンタメニュースブログアプリ教材」より、記事詳細ページを解説。生成AI、AI活用、DX、業務改善を実践しながら学べるオンライン教材です。

6Next.jsとTailwind CSSで作る 岐阜県ローカルエンタメニュースブログアプリ教材Next.js / Tailwind CSS / アプリ開発 / 開発 / Web開発

OVERVIEW

この節で学べること

概要を表示する
項目内容
教材名Next.jsによるWebアプリケーション開発概論
Next.jsとTailwind CSSで作る 岐阜県ローカルエンタメニュースブログアプリ教材
記事詳細ページ
カテゴリNext.js / Tailwind CSS / アプリ開発 / 開発 / Web開発
学習内容生成AI、AI活用、DX、業務改善を実践しながら理解するための教材です。

TABLE OF CONTENTS

目次

CONTENT

ここから

14. 記事詳細ページを作る

app/posts/[slug]/page.tsx を作成します。

// app/posts/[slug]/page.tsx

import { CategoryBadge } from '@/components/CategoryBadge';
import { getPostBySlug, getSortedPosts } from '@/data/posts';
import type { Metadata } from 'next';
import Link from 'next/link';
import { notFound } from 'next/navigation';

type PostPageProps = {
  params: Promise<{
    slug: string;
  }>;
};

/**
 * 役割: 静的生成する記事slug一覧をNext.jsへ渡す
 * 入力: なし
 * 出力: slug配列
 */
export function generateStaticParams() {
  return getSortedPosts().map((post) => ({
    slug: post.slug,
  }));
}

/**
 * 役割: 記事詳細ページのメタデータを生成する
 * 入力: URLパラメータ
 * 出力: titleとdescriptionを含むメタデータ
 */
export async function generateMetadata({
  params,
}: PostPageProps): Promise<Metadata> {
  const { slug } = await params;
  const post = getPostBySlug(slug);

  if (!post) {
    return {
      title: '記事が見つかりません',
    };
  }

  return {
    title: `${post.title} | 岐阜ローカルエンタメ通信`,
    description: post.description,
  };
}

/**
 * 役割: 記事詳細ページを表示する
 * 入力: URLパラメータ
 * 出力: 記事本文、タグ、戻るリンクを含む詳細ページ
 */
export default async function PostPage({ params }: PostPageProps) {
  const { slug } = await params;
  const post = getPostBySlug(slug);

  if (!post) {
    notFound();
  }

  return (
    <main className="mx-auto max-w-3xl px-5 py-14">
      <Link
        href="/"
        className="text-sm font-bold text-slate-500 transition hover:text-slate-950"
      >
        ← 記事一覧へ戻る
      </Link>

      <article className="mt-8">
        <div className="mb-5 flex flex-wrap items-center gap-3">
          <CategoryBadge category={post.category} />

          <time className="text-xs font-bold text-slate-400">
            {post.publishedAt}
          </time>

          <span className="text-xs font-bold text-slate-400">
            {post.readingMinutes}分で読める
          </span>
        </div>

        <h1 className="text-4xl font-black leading-tight tracking-tight text-slate-950 md:text-5xl">
          {post.title}
        </h1>

        <p className="mt-6 text-lg leading-9 text-slate-600">
          {post.description}
        </p>

        <div className="mt-8 flex flex-wrap gap-2">
          {post.tags.map((tag) => (
            <span
              key={tag}
              className="rounded-full bg-slate-100 px-3 py-1 text-xs font-bold text-slate-500"
            >
              #{tag}
            </span>
          ))}
        </div>

        <div className="mt-12 space-y-7 border-t border-slate-200 pt-10">
          {post.body.map((paragraph) => (
            <p
              key={paragraph}
              className="text-base leading-9 text-slate-700 md:text-lg"
            >
              {paragraph}
            </p>
          ))}
        </div>
      </article>
    </main>
  );
}

[slug]** とは何か**

[slug] は、URLの一部を変数として受け取る仕組みです。

例えば、次のURLがあるとします。

/posts/gifu-riverside-night-market

このとき、slug には以下が入ります。

gifu-riverside-night-market

そして、このslugを使って記事データを探します。

const post = getPostBySlug(slug);

これで記事詳細ページが表示できます。


15. 表示確認をする

ターミナルで以下を実行します。

npm run dev

ブラウザで開きます。

http://localhost:3000

確認するポイントは以下です。

確認項目OKの状態
トップページ記事カードが表示される
検索「大垣」などで絞り込める
詳細ページ記事をクリックすると移動する
戻るリンク詳細ページから一覧に戻れる
スマホ表示1列で自然に表示される

FAQ

よくある質問

記事詳細ページは医療関係者向けだけの内容ですか。
医療分野の例が含まれる場合もありますが、医療関係者だけに限定した内容ではありません。生成AI、AI活用、DX、業務改善、プロトタイプ開発など、一般的なAI学習の事例として読める内容です。
AI初心者でも読めますか。
はい。AIをこれから学ぶ方、数学が苦手な方、仕事でAIを使いたい方にも読み進めやすいように、教材の章と節の流れに沿って整理しています。
サムネイル画像は必ず表示されますか。
はい。教材にcoverUrlが設定されている場合はその画像を表示し、未設定の場合は代替サムネイル画像を表示します。
Next.jsによるWebアプリケーション開発概論のほかの章も読めますか。
はい。教材トップから章立てを確認でき、前後の節へもページ下部のナビゲーションから移動できます。