用 Claude Code 实现可访问面包屑导航
用 Claude Code 实现面包屑导航、JSON-LD、aria-current、移动端样式和测试。
面包屑导航看起来只是标题上方的一行小链接,但在真实站点里,它同时影响站点结构、内部链接、可访问性、结构化数据和移动端阅读体验。实现得不好时,页面看起来仍然正常,屏幕阅读器却无法识别当前页,JSON-LD 里混入相对 URL,或者长标题在手机上把正文挤到很下面。
我在 ClaudeCodeLab 模板中试过让 Claude Code 直接“做一个 breadcrumb”。第一版确实画出了 Home > Blog > Title,但缺少 aria-current,JSON-LD 没有绝对 URL,移动端换行严重,动态路由也直接显示 slug。解决办法不是完全手写,而是在提示词里把完成标准说清楚。
本文会用 React/Next.js/Astro 风格的站点为例,说明如何让 Claude Code 搭建并审查可访问的面包屑导航。建议同时阅读 SEO 优化、可访问性实现、Astro 开发 和 React 开发。
设计边界
面包屑不是浏览器返回按钮的替代品。它的作用是表达层级、让用户快速回到父页面,并帮助搜索引擎理解页面关系。WAI-ARIA APG 的 Breadcrumb Pattern 要求将面包屑放进带标签的导航区域,并用 aria-current="page" 表示当前页。
结构化数据应遵循 schema.org BreadcrumbList。BreadcrumbList 表示一串网页,position 用来明确顺序。如果希望 Google 搜索结果使用面包屑,还要参考 Google Search Central 的面包屑结构化数据。
| 决策 | 要确定什么 | 常见失败 |
|---|---|---|
| 标签 | 使用标题、分类名还是字典覆盖 | 直接显示 claude-code-breadcrumb-navigation |
| URL | HTML 用相对路径,JSON-LD 用绝对 URL | 结构化数据里出现相对路径 |
| 当前页 | 最后一项是链接还是文本 | 只用颜色表示当前页 |
| 移动端 | 全部显示还是隐藏中间项 | 面包屑折成多行 |
| 多语言 | 路由前缀和翻译标签 | 中文页混入其他语言标签 |
给 Claude Code 的提示词
不要只说“做一个面包屑组件”。要把数据生成、UI、JSON-LD、响应式样式和测试一起交给 Claude Code。
为 React/Next.js 或 Astro 站点实现面包屑导航。
要求:
- items 使用 { label: string; href: string }[]。
- 最后一项加 aria-current="page"。
- 使用 nav aria-label="面包屑导航"。
- 分隔符加 aria-hidden="true"。
- 从同一个 items 数组生成 JSON-LD BreadcrumbList。
- JSON-LD 的 URL 用 siteUrl 转成绝对 URL。
- 增加从 pathname 生成 items 的工具函数。
- slug 要转成人类可读文本,并支持标签字典覆盖。
- 移动端隐藏中间项,但保留当前页可读。
- 用 Vitest 覆盖根路径、深层路径、本地化标签和 query string。
- 完成后列出可访问性和结构化数据检查项。
这会让 Claude Code 产出可审查的实现,而不是只给一个漂亮片段。较大的改动可以再用 Claude Code 审查流程清单 做第二轮检查。
React 组件
创建 components/Breadcrumb.tsx。siteUrl 传入不带末尾斜杠的域名,例如 https://example.com。画面和 JSON-LD 都从同一个 items 生成,避免分类或标题变化时不同步。
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) }}
/>
</>
);
}
最后一项这里渲染为文本;即便如此,显式保留 aria-current="page" 也方便自动化测试。分隔符只是视觉符号,所以要从辅助技术中隐藏。
从路由生成 items
这个工具函数处理 query string、末尾斜杠、URL 编码和标签覆盖。实际项目中可以把最后一项替换成 CMS 或 frontmatter 的标题。
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;
}
Next.js/Astro 使用方式
Next.js 中可以在页面或布局里根据路由和文章元数据生成标签。Astro 中则可以用 Astro.url.pathname 和 frontmatter 的 title。关键是让 Claude Code 知道哪个数据源才是标准。
import { Breadcrumb } from "@/components/Breadcrumb";
import { buildBreadcrumbs } from "@/lib/breadcrumbs";
const siteUrl = "https://claudecodelab.com";
export default async function ArticlePage() {
const pathname = "/zh/blog/claude-code-breadcrumb-navigation";
const labels = {
"/": "首页",
"/zh": "中文",
"/zh/blog": "文章",
"/zh/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>
);
}
Astro 组件也可以使用同样的 JSON-LD 结构:在 .astro 文件里接收 items 和 siteUrl,用 set:html={JSON.stringify(jsonLd)} 输出脚本即可。静态博客通常更适合这种做法,因为构建时就能生成结构化数据。
移动端 CSS
小屏幕上保留完整 DOM 和 JSON-LD,但视觉上隐藏中间层级,让当前页保持可读。
.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;
}
}
测试
先测试路径生成,再用 Testing Library 或 Playwright 检查实际渲染。只看截图很容易漏掉语义错误。
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("/zh/blog/claude-code-breadcrumb-navigation", {
"/": "首页",
"/zh": "中文",
"/zh/blog": "文章",
"/zh/blog/claude-code-breadcrumb-navigation": "面包屑导航",
}),
).toEqual([
{ label: "首页", href: "/" },
{ label: "中文", href: "/zh" },
{ label: "文章", href: "/zh/blog" },
{ label: "面包屑导航", href: "/zh/blog/claude-code-breadcrumb-navigation" },
]);
});
});
E2E 里检查 nav[aria-label]、唯一的 aria-current="page"、可解析的 JSON-LD、绝对 URL,以及手机宽度下不遮挡标题。具体流程可参考 Playwright 测试。
真实用例
第一,博客和文档站。首页 > 文章 > Claude Code > 标题 可以把读者带回主题页,并增强内部链接。
第二,电商和付费课程页。首页 > 培训 > Claude Code > 团队研修 能把教程流量自然引向 培训与咨询。
第三,SaaS 和管理后台。组织 > 项目 > 设置 > 账单 这样的深层路径,可以减少用户在敏感设置页中的迷路感。
第四,多语言站点。路由前缀、可见标签和 JSON-LD 的 name 必须一起本地化。
常见陷阱
不要只用颜色表示当前页。不要让 JSON-LD 保留相对 URL。不要把 slug 当作最终文案。不要让移动端面包屑无限换行。也不要用两套数组分别生成画面和结构化数据;分类调整后它们一定会不同步。
发布前检查
nav有清晰的aria-label- 当前页有
aria-current="page" - 分隔符不会被读出
- JSON-LD 的类型是
BreadcrumbList position从 1 开始且顺序正确- JSON-LD URL 是生产域名下的绝对 URL
- 可见面包屑和 JSON-LD 表示同一层级
- 移动端不与标题或导言重叠
- 多语言标签和 URL 符合当前语言
- Rich Results Test 或 Search Console 验证通过
最后可以让 Claude Code 按 WAI-ARIA APG、schema.org BreadcrumbList 和 Google Search 结构化数据进行批判性审查。第二轮审查通常能发现第一轮生成忽略的小问题。
CTA 与验证结果
面包屑是小组件,但会影响内容站的回游和商业转化。ClaudeCodeLab 会把教程读者引向免费速查表、产品和 Claude Code 培训咨询。如果团队想把这套提示词、审查清单和落地流程应用到自己的仓库,培训咨询就是下一步。
我将本文代码拆成 React 组件、路由工具和 Vitest 示例进行确认。最有价值的改动是让可见导航和 JSON-LD 共用同一个 items 数组;早期版本用两套数据,分类名一改就不一致。统一数据源后,Claude Code 的修复指令也更简单。
总结
好的面包屑导航不只是链接之间的 >。请把标签、路由、aria-current、JSON-LD、绝对 URL、移动端表现和测试一次性交给 Claude Code,并在发布前按官方资料审查。这样,小组件也能同时提升 UX、SEO 和商业导线。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
Claude Code权限安全阶梯:逐步放开访问而不失控
从只读到有限编辑、验证命令和部署检查的 Claude Code 权限升级流程。
Claude Code 小PR证据包:让小改动真正可审查
用差异、验证命令、公开URL、CTA路径和回滚说明,把Claude Code的小PR变得可审查。
Claude Code 提交前 Review Gate:同时检查差异、测试、公开 URL 和 CTA
提交前用 Claude Code 审查差异范围、build、公开 URL、Gumroad 链接、咨询 CTA、缺少测试和无关文件。