Next.js ブログにカテゴリ一覧・タグ一覧ページを追加する
目次
課題
このブログには個別のカテゴリページ(/ja/category/programming)とタグページ(/ja/tag/nextjs)があったが、全カテゴリ・全タグを俯瞰する手段がなかった。記事のバッジからは個別ページに遷移できるものの、「このブログにはどんなコンテンツがあるのか」を把握する一覧ページが存在しない。
両ロケールにカテゴリ一覧とタグ一覧のページを追加したかった。
データ層のコード追加はゼロ
この実装で満足だったのは、必要な関数がすべて src/lib/posts.ts に既存だったことだ:
getAllCategories()— 全カテゴリオブジェクトの取得getCategoryName()/getCategoryDescription()— バイリンガル対応の名前・説明getPostsByCategory()— カテゴリ別記事取得(件数カウント用)getAllTags(locale)— ロケール別の全タグ取得getPostsByTag()— タグ別記事取得(件数カウント用)
新しいユーティリティも、新しいコンポーネントも、新しいデータ取得パターンも不要。既存の関数を新しいページに組み合わせるだけだった。データ層を最初にきちんと設計した成果が出た形だ。
カードグリッド vs タグクラウド:なぜ異なるレイアウトか
2つの一覧ページは異なるレイアウトを使っている。理由はデータの形の違いにある。
カテゴリ一覧 はカードグリッド(grid gap-4 sm:grid-cols-2)。各カテゴリには名前・説明・記事数という構造化データがあり、カード形式に自然に収まる。カテゴリ数は少ない(現在1つ)ので、各カテゴリに視覚的な存在感を持たせたい:
<Link className="rounded-lg border border-border p-4 transition-colors hover:bg-accent">
<h2 className="font-semibold">{name}</h2>
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
<p className="mt-2 text-xs text-muted-foreground">{postCount}件の記事</p>
</Link>タグ一覧 はフレックスのタグクラウド。タグは説明のないフラットな文字列 — 名前と件数だけ。数が多いので密なレイアウトが合う:
<Link className="inline-flex items-center gap-1.5 rounded-full border border-border px-3 py-1.5 text-sm">
#{tag} <span className="text-xs">({postCount})</span>
</Link>タグクラウドのバッジは既存の TagBadge(px-2.5 py-0.5 text-xs)より意図的に大きくし(px-3 py-1.5 text-sm)、一覧ページでの視認性とタップしやすさを確保している。
ロケールの非対称性
設計上の微妙な違いとして、getAllTags(locale) はロケールを引数に取るが、getAllCategories() は取らない。これは定義方法の違いを反映している:
- カテゴリ は静的 YAML ファイル — ロケールに関係なく同じカテゴリが存在し、
name/nameJaとdescription/descriptionJaでバイリンガル対応 - タグ は記事フロントマターのフリーテキスト — 日本語記事と英語記事でまったく異なるタグセットになりうる
つまりタグ一覧はロケールごとに異なるコンテンツを表示でき、カテゴリ一覧は同じカテゴリを翻訳された名前で表示する。バイリンガルなコンテンツシステムを作る際に重要な区別だ。
まとめ
- 既存の関数がきれいに組み合わさる — データ層のコード追加ゼロで新ページが作れたのは、
posts.tsのヘルパー関数設計が正しかった証拠だ。抽象化が適切なら、新機能はコンポジションだけで済む。 - データの形がレイアウトを決める — カテゴリ(構造化、少数)→ カードグリッド、タグ(フラット、多数)→ タグクラウド。根本的に異なるデータに同じレイアウトを強制しないこと。
- 静的 vs 動的メタデータの設計含意 — YAML 定義のカテゴリはロケール非依存、フロントマターのタグはロケール依存。この非対称性がバイリンガル一覧ページに影響する。
