Claude Codeでパンくずリストを実装する実践ガイド
Claude Codeでアクセシブルなパンくずリスト、JSON-LD、モバイル表示を実装する手順を解説。
パンくずリストは、記事の上に小さく置かれるだけの地味なUIに見えます。しかし実務では、サイト構造、内部リンク、アクセシビリティ、Google検索の表示、モバイルの読みやすさが一か所に集まる重要な部品です。ここを雑に実装すると、見た目は問題なくても、スクリーンリーダーでは現在地が伝わらない、構造化データが壊れる、カテゴリ変更で古いURLが残る、といった不具合が出ます。
Claude Codeに「パンくずを作って」とだけ頼むと、Home > Blog > Titleを描画するだけの部品になりがちです。最初からaria-current、JSON-LD、スマホ表示、動的ルート名の扱いまで完了条件に入れると、修正回数はかなり減ります。
この記事では、React/Next.js/Astro風のサイトで使えるパンくずリストを、Claude Codeにどう作らせ、どうレビューするかをまとめます。関連する基礎はSEO最適化とアクセシビリティ実装も合わせて確認してください。
先に決めること
パンくずリストの目的は「戻るボタンの代わり」ではありません。階層を伝え、親ページへ移動しやすくし、検索エンジンにもページ同士の関係を伝えることです。WAI-ARIA APGのBreadcrumb Patternでは、パンくずをナビゲーションランドマークに入れ、ラベルを付け、現在ページのリンクにはaria-current="page"を設定する考え方が示されています。
構造化データは、schema.org BreadcrumbListの形に合わせます。BreadcrumbListはWebページの連なりを表し、順序を復元するためにpositionを使います。Google検索での表示を狙う場合は、Google Search Centralのパンくず構造化データも確認します。ここで重要なのは、画面上のパンくずとJSON-LDの内容を別物にしないことです。
実装前に決める項目は次の通りです。
| 項目 | 決める内容 | 失敗しやすい点 |
|---|---|---|
| ラベル | URLセグメントをそのまま出すか、記事タイトルやカテゴリ名を使うか | claude-code-breadcrumb-navigationのようなslugが表示される |
| URL | すべて絶対URLにできるか | JSON-LDに相対URLが混ざる |
| 現在ページ | リンクにするか、テキストにするか | aria-currentがなく色だけで表す |
| モバイル | 全階層を出すか、中間を省略するか | 2行、3行に折り返して本文を押し下げる |
| 多言語 | /en/や/ko/などのロケールをどう扱うか | 日本語ラベルが英語ページに残る |
Claude Codeへの依頼文
Claude Codeには、コンポーネント単体ではなく「データ生成、表示、構造化データ、テスト」をまとめて依頼します。対象フレームワーク、URLの扱い、アクセシビリティ要件を最初に固定してください。
React/Next.jsまたはAstroで使えるパンくずリストを実装してください。
要件:
- itemsは { label: string; href: string } の配列で受け取る
- 最後の項目は現在ページとして aria-current="page" を付ける
- navには aria-label="パンくずリスト" を付ける
- 区切り記号は aria-hidden="true" にする
- JSON-LD BreadcrumbListを同じitemsから生成する
- JSON-LDのURLは siteUrl から絶対URLにする
- URL pathnameからitemsを生成するユーティリティも作る
- slugは人間向けに整形し、必要ならラベル辞書で上書きする
- モバイルでは中間階層を省略し、最後の項目を読めるようにする
- Vitestでルート、深い階層、日本語ラベル、クエリ付きURLをテストする
- 実装後にアクセシビリティと構造化データの確認観点を出す
この依頼文のポイントは、Claude Codeに「きれいなUI」ではなく「壊れにくい仕様」を作らせることです。生成された差分は、Claude Codeレビュー・ワークフローチェックリストのように、要件ごとに確認すると見落としを減らせます。
Reactコンポーネント
まずはReactで使える基本コンポーネントです。Next.jsならcomponents/Breadcrumb.tsx、AstroでReact integrationを使っている場合も同じ発想で配置できます。siteUrlはhttps://example.comのように末尾スラッシュなしで渡します。
import type { ReactNode } from "react";
export type BreadcrumbItem = {
label: string;
href: string;
};
type BreadcrumbProps = {
items: BreadcrumbItem[];
siteUrl: string;
ariaLabel?: string;
};
function toAbsoluteUrl(siteUrl: string, href: string) {
return new URL(href, siteUrl).toString();
}
function Separator(): ReactNode {
return (
<span className="breadcrumb__separator" aria-hidden="true">
/
</span>
);
}
export function Breadcrumb({
items,
siteUrl,
ariaLabel = "Breadcrumb",
}: BreadcrumbProps) {
if (items.length <= 1) return null;
const jsonLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: items.map((item, index) => ({
"@type": "ListItem",
position: index + 1,
item: {
"@id": toAbsoluteUrl(siteUrl, item.href),
name: item.label,
},
})),
};
return (
<>
<nav className="breadcrumb" aria-label={ariaLabel}>
<ol className="breadcrumb__list">
{items.map((item, index) => {
const isCurrent = index === items.length - 1;
return (
<li className="breadcrumb__item" key={item.href}>
{index > 0 ? <Separator /> : null}
{isCurrent ? (
<span className="breadcrumb__current" aria-current="page">
{item.label}
</span>
) : (
<a className="breadcrumb__link" href={item.href}>
{item.label}
</a>
)}
</li>
);
})}
</ol>
</nav>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</>
);
}
現在ページをリンクにしない実装にしているため、WAI-ARIA APG上はaria-currentが必須ではありません。それでもテストしやすく、支援技術にも意図が伝わりやすいため、この例では現在項目に明示しています。区切り記号は視覚上の記号なので、読み上げ対象にしないようaria-hidden="true"を付けます。
URLからitemsを作る
次に、ルーティングデータからパンくずを生成します。単純にpathname.split("/")するだけだと、URLエンコード、クエリ文字列、末尾スラッシュ、動的slugの表示名で壊れます。最低限の整形とラベル辞書を用意しておくと、記事、カテゴリ、商品詳細、管理画面で使い回せます。
import type { BreadcrumbItem } from "@/components/Breadcrumb";
export type BreadcrumbLabels = Record<string, string>;
function titleize(segment: string) {
return decodeURIComponent(segment)
.replace(/[-_]+/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase());
}
export function buildBreadcrumbs(
pathname: string,
labels: BreadcrumbLabels = {},
): BreadcrumbItem[] {
const cleanPath = pathname.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
const segments = cleanPath.split("/").filter(Boolean);
const items: BreadcrumbItem[] = [
{ label: labels["/"] ?? "Home", href: "/" },
];
let href = "";
for (const segment of segments) {
href += `/${segment}`;
items.push({
label: labels[href] ?? labels[segment] ?? titleize(segment),
href,
});
}
return items;
}
labelsには、ルート単位でもセグメント単位でも上書きを入れられます。たとえば"/blog": "記事"、"claude-code-breadcrumb-navigation": "パンくずリスト実装"のように指定します。CMSやAstroのcontent collectionsを使っている場合は、記事frontmatterのtitleを最後のラベルに使うほうが自然です。
Next.jsで使う例
Next.js App Routerでは、レイアウトやページでpathname相当の情報を作り、記事タイトルやカテゴリ名をラベル辞書として渡します。サーバーコンポーネントであれば、CMSやDBから取得した正式タイトルをそのまま使えます。
import { Breadcrumb } from "@/components/Breadcrumb";
import { buildBreadcrumbs } from "@/lib/breadcrumbs";
const siteUrl = "https://claudecodelab.com";
export default async function ArticlePage() {
const pathname = "/blog/claude-code-breadcrumb-navigation";
const labels = {
"/": "ホーム",
"/blog": "記事",
"/blog/claude-code-breadcrumb-navigation":
"Claude Codeでパンくずリストを実装する実践ガイド",
};
const items = buildBreadcrumbs(pathname, labels);
return (
<main>
<Breadcrumb items={items} siteUrl={siteUrl} ariaLabel="パンくずリスト" />
<h1>Claude Codeでパンくずリストを実装する実践ガイド</h1>
</main>
);
}
実務ではpathnameを手書きし続けるのではなく、ルート定義、CMSのslug、カテゴリ階層から組み立てます。Claude Codeには「どのデータが正か」を伝えてください。Next.jsのルート、AstroのAstro.url.pathname、React RouterのuseLocation()など、正とする情報源が曖昧だと、表示とJSON-LDがずれます。
Astroで使う例
Astroだけで完結させるなら、同じ考え方を.astroコンポーネントにできます。Reactを入れない静的ブログではこちらのほうが軽く、記事ビルド時にJSON-LDまで出力できます。
---
type BreadcrumbItem = {
label: string;
href: string;
};
const { items, siteUrl, ariaLabel = "パンくずリスト" } = Astro.props as {
items: BreadcrumbItem[];
siteUrl: string;
ariaLabel?: string;
};
const jsonLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: items.map((item, index) => ({
"@type": "ListItem",
position: index + 1,
item: {
"@id": new URL(item.href, siteUrl).toString(),
name: item.label,
},
})),
};
---
{items.length > 1 && (
<>
<nav class="breadcrumb" aria-label={ariaLabel}>
<ol class="breadcrumb__list">
{items.map((item, index) => {
const isCurrent = index === items.length - 1;
return (
<li class="breadcrumb__item">
{index > 0 && <span class="breadcrumb__separator" aria-hidden="true">/</span>}
{isCurrent ? (
<span class="breadcrumb__current" aria-current="page">{item.label}</span>
) : (
<a class="breadcrumb__link" href={item.href}>{item.label}</a>
)}
</li>
);
})}
</ol>
</nav>
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
</>
)}
AstroではAstro.url.pathnameを使って現在パスを取れます。記事ページなら、frontmatterのtitle、category、ロケール情報を合わせてラベルを組み立てると、多言語サイトでも自然な表示になります。
モバイル表示のCSS
パンくずは小さいUIですが、スマホでは長い記事タイトルがすぐに画面を占有します。全階層を無理に見せるより、ホームと直前カテゴリ、現在ページを残し、中間を省略するほうが読みやすいことがあります。
.breadcrumb {
margin-block: 0 1rem;
font-size: 0.875rem;
color: #4b5563;
}
.breadcrumb__list {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
list-style: none;
margin: 0;
padding: 0;
}
.breadcrumb__item {
align-items: center;
display: inline-flex;
min-width: 0;
}
.breadcrumb__link {
color: #2563eb;
text-decoration: underline;
text-underline-offset: 0.15em;
}
.breadcrumb__current {
color: #111827;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.breadcrumb__separator {
color: #9ca3af;
margin-inline: 0.35rem;
}
@media (max-width: 640px) {
.breadcrumb__list {
flex-wrap: nowrap;
}
.breadcrumb__item:not(:first-child):not(:nth-last-child(-n + 2)) {
display: none;
}
.breadcrumb__item:nth-last-child(2)::after {
color: #9ca3af;
content: "...";
margin-inline: 0.35rem;
}
.breadcrumb__current {
max-width: 58vw;
}
}
省略はCSSだけで行い、DOMとJSON-LDには全階層を残します。画面上で中間項目を隠しても、構造化データまで削る必要はありません。逆に、画面には出していない架空のカテゴリをJSON-LDだけに入れるのは避けます。
テストを書く
パンくずの失敗は、見た目確認だけでは見逃されます。生成ユーティリティはVitestで境界値を押さえ、UIはTesting LibraryやPlaywrightでaria-currentとJSON-LDを確認します。
import { describe, expect, it } from "vitest";
import { buildBreadcrumbs } from "./breadcrumbs";
describe("buildBreadcrumbs", () => {
it("returns only Home for the root path", () => {
expect(buildBreadcrumbs("/")).toEqual([{ label: "Home", href: "/" }]);
});
it("builds nested breadcrumbs and ignores query strings", () => {
expect(buildBreadcrumbs("/blog/claude-code?page=2")).toEqual([
{ label: "Home", href: "/" },
{ label: "Blog", href: "/blog" },
{ label: "Claude Code", href: "/blog/claude-code" },
]);
});
it("uses localized labels when provided", () => {
expect(
buildBreadcrumbs("/blog/claude-code-breadcrumb-navigation", {
"/": "ホーム",
"/blog": "記事",
"/blog/claude-code-breadcrumb-navigation": "パンくずリスト実装",
}),
).toEqual([
{ label: "ホーム", href: "/" },
{ label: "記事", href: "/blog" },
{
label: "パンくずリスト実装",
href: "/blog/claude-code-breadcrumb-navigation",
},
]);
});
});
E2Eでは、現在ページの要素にaria-current="page"があること、nav[aria-label]で取得できること、JSON-LDがBreadcrumbListとしてパースできること、スマホ幅で本文と重ならないことを確認します。Claude CodeでPlaywrightテストを書くを使うと、表示確認まで自動化しやすくなります。
3つ以上の実務ユースケース
1つ目は、ブログやドキュメントサイトです。ホーム > 記事 > Claude Code > タイトルのように置くと、読者がカテゴリへ戻りやすくなります。SEO記事、チュートリアル、APIリファレンスでは内部リンクの補助にもなります。
2つ目は、ECサイトや教材販売ページです。ホーム > 教材 > Claude Code > チーム研修のような階層は、ユーザーが上位カテゴリを見直す導線になります。比較記事や導入事例からトレーニング・相談ページへ自然に流すと、広告だけに頼らない収益導線になります。
3つ目は、SaaSや管理画面です。組織 > プロジェクト > 設定 > 請求のような深い画面では、サイドバーだけでは現在地が伝わりにくいことがあります。パンくずがあると、権限設定や請求設定のような緊張感のある画面でも、上位階層へ戻りやすくなります。
よくある落とし穴
一番多い失敗は、最後の項目を色だけで現在ページに見せることです。視覚的には分かっても、支援技術や自動テストには伝わりません。現在項目にはaria-current="page"を付け、リンクにするかテキストにするかを設計として決めます。
次に、JSON-LDのURLを相対パスのまま出す失敗です。画面のリンクは/blog/exampleでも動きますが、構造化データではsiteUrlから絶対URLを作るほうが安全です。本番、ステージング、ローカルでsiteUrlが混ざらないよう、環境変数もClaude Codeに確認させます。
三つ目は、slugをそのまま表示する失敗です。api-design-assistantは開発者には読めても、一般読者には雑な印象になります。記事タイトル、カテゴリ名、商品名、チーム名など、人間向けのラベルを必ず渡します。
四つ目は、モバイルで長いタイトルが折り返し続ける失敗です。パンくずは本文前に置かれるため、ここが大きすぎると導入文まで遠くなります。CSSで中間階層を省略し、現在ページは省略記号で収めます。
五つ目は、画面表示とJSON-LDを別々の配列から作ることです。カテゴリ変更や記事移動のときに片方だけ更新され、Search Consoleでエラーになります。同じitemsから画面とJSON-LDを生成する設計にしてください。
公開前チェックリスト
navに分かりやすいaria-labelがある- 現在ページに
aria-current="page"がある - 区切り記号が読み上げ対象になっていない
- JSON-LDの
@typeがBreadcrumbListになっている positionが1から順番に並んでいる@idが本番ドメインの絶対URLになっている- 画面表示とJSON-LDが同じ階層を表している
- モバイル幅で本文や見出しと重ならない
- 多言語ページでラベルとURLがその言語に合っている
- Search ConsoleやRich Results Testで構造化データを検証した
Claude Codeに最後のレビューを頼むなら、「この差分をWAI-ARIA APG、schema.org BreadcrumbList、Google Searchのパンくず構造化データの観点で批判的にレビューして」と依頼します。生成だけで終わらせず、別パスでレビューさせるのが実務では効きます。
収益導線へのつなげ方
パンくずリスト自体は小さな改善ですが、記事サイトや教材サイトでは回遊率、カテゴリ閲覧、相談導線に効きます。ClaudeCodeLabでは、実装記事の末尾から無料チートシートやClaude Codeトレーニング・相談へつなげるとき、パンくずと内部リンクが「今どのテーマを読んでいるか」を補助します。
チームでClaude Codeを導入する場合は、パンくずのような小さなUI部品から始めるのが現実的です。要件が明確で、アクセシビリティとSEOの確認観点も見えやすく、レビュー練習に向いています。
実際に試した結果
この記事のコードは、Reactコンポーネント、URL生成ユーティリティ、Vitestの境界テストとして切り分けて確認しました。特に効果が大きかったのは、同じitemsから画面表示とJSON-LDを生成する設計です。最初に別々の配列で試したときはカテゴリ名の変更漏れが起きましたが、共通化するとレビュー観点が「itemsが正しいか」に集約され、Claude Codeにも修正指示を出しやすくなりました。
まとめ
パンくずリストは、デザインの飾りではなく、サイト構造をユーザーと検索エンジンの両方に伝える実装です。Claude Codeに任せるときは、aria-current、JSON-LD、絶対URL、ラベル辞書、モバイル表示、テストまで最初の依頼文に含めてください。
小さなコンポーネントでも、公式仕様に沿って作り、手元で検証し、収益導線につなげると、記事サイト全体の品質が上がります。次はPlaywrightテストでナビゲーション全体を点検してください。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Code権限セーフティラダー: 初心者がallowを広げる順番
Claude Codeの権限をread-onlyからbuild、限定編集、deploy確認まで段階的に広げる安全な運用手順。
Claude Code Small PR Proof Pack: 小さなPRをレビュー可能にする証拠セット
Claude Codeの小さなPRに、差分・検証・公開URL・CTA・rollbackを添える実務チェックリスト。
Claude Codeのコミット前レビューゲート: 差分、テスト、CTAをまとめて止める型
Claude Codeでcommit前に差分をレビューする実践手順。build、公開URL、CTA、Gumroadリンク、未翻訳本文を検知します。