Knowledge
AppHeader のパンくずリストを多段階構成に変更した記録
1段階だったブレッドクラム(HB DevTools > ページ名)を、カテゴリを挟んだ多段階構成(HB DevTools > Tools > JSON Formatter)に変更した実装記録。BreadcrumbSegment 型の導入と useHeaderState フックの返り値変更を中心に解説します。
1. 変更前の課題
変更前の AppHeader のブレッドクラムは1段階のみ(例: HB DevTools > JSON Formatter)でした。 ツールページ・ドキュメントページ・ナレッジ記事がすべて同じフラットな構造で表示されるため、 ユーザーが「いま自分はサイトのどの領域にいるか」を把握しにくい状態でした。
変更前のコードの核心部分は以下の通りです。
function useHeaderState(): { isHome: boolean; pageLabel: string | null } {
const pathname = usePathname()
// ...
const tool = tools.find((t) => t.href === pathname)
if (tool) return { isHome: false, pageLabel: tool.label }
// ...
}返り値が pageLabel: string | null の1値のため、 カテゴリ階層を表現する手段がありませんでした。
2. 変更方針
各パスで表示するブレッドクラムを以下のように設計しました。
- ツールページ(
/tools/*):HB DevTools > Tools > JSON Formatter/toolsインデックスページが存在しないため、Toolsはリンクなしのプレーンテキストにする - Docs 記事(
/docs/*):HB DevTools > Technical Docs > JSON Formatter の設計/docsは実ページがあるため中間セグメントをリンクにする - Knowledge 記事(
/knowledge/*):HB DevTools > Knowledge > 記事タイトル
同様に/knowledgeをリンクにする - インデックス・静的ページ(
/docs,/about等): 1セグメントのまま変更なし
3. BreadcrumbSegment 型の導入
1値の string | null では複数セグメントを表現できないため、 セグメント1件を表す型を新設しました。
type BreadcrumbSegment = { label: string; href?: string }href を省略可能にすることで、 リンクにするかプレーンテキストにするかをデータ側で制御できます。 描画層では href の有無だけを見ればよいため、 条件分岐がシンプルになります。
4. useHeaderState の変更
フックの返り値を pageLabel: string | null からbreadcrumbs: BreadcrumbSegment[] に変更しました。
// Before
function useHeaderState(): { isHome: boolean; pageLabel: string | null }
// After
function useHeaderState(): { isHome: boolean; breadcrumbs: BreadcrumbSegment[] }各パスの解決ロジックは以下のようになります。
const tool = tools.find((t) => t.href === pathname)
if (tool) return {
isHome: false,
breadcrumbs: [{ label: 'Tools' }, { label: tool.label }],
}
const doc = docs.find((d) => d.href === pathname)
if (doc) return {
isHome: false,
breadcrumbs: [{ label: 'Technical Docs', href: '/docs' }, { label: doc.label }],
}
const article = knowledge.find((k) => k.href === pathname)
if (article) return {
isHome: false,
breadcrumbs: [{ label: 'Knowledge', href: '/knowledge' }, { label: article.label }],
}ツール(Tools)は href なし、 Docs・Knowledge の中間セグメントは href あり、 という差異をデータで表現しています。
5. 描画ロジックの変更
単一の pageLabel を表示していた箇所を、breadcrumbs 配列のマップに置き換えました。 各セグメントの間に ChevronRight アイコンを挟む必要があるため、 React の Fragment を使ってラッパーなしで並べています。
{breadcrumbs.map((crumb, i) => {
const isLast = i === breadcrumbs.length - 1
return (
<Fragment key={crumb.label}>
<ChevronRight className='size-3.5 text-muted-foreground/60' aria-hidden='true' />
{isLast ? (
<span className='font-medium text-muted-foreground' aria-current='page'>
{crumb.label}
</span>
) : crumb.href ? (
<Link href={crumb.href} className='... hover:text-foreground'>
{crumb.label}
</Link>
) : (
<span className='font-medium text-muted-foreground/70'>{crumb.label}</span>
)}
</Fragment>
)
})}セグメントの種別は3パターンに分類されます。
- 最後のセグメント(現在ページ):
aria-current='page'付きのspan - 中間セグメント・リンクあり:
hover:text-foreground付きのLink - 中間セグメント・リンクなし(Tools): プレーンな
span
6. アクセシビリティ対応
ブレッドクラムに関するアクセシビリティ上の変更点は2つです。
- コンテナを
divからnavに変更:<nav aria-label="パンくずリスト">とすることで、 スクリーンリーダーがナビゲーションランドマークとして認識できるようになります。 - 現在ページに
aria-current='page'を付与: 最後のセグメント(現在閲覧中のページ名)のspanにこの属性を追加し、 スクリーンリーダーに「このリンクが現在のページです」と明示します。
ChevronRight アイコンは装飾目的のため aria-hidden='true' を維持しています。