Tips & Tricks (更新: 2026/6/2)

用 Claude Code 实现可访问面包屑导航

用 Claude Code 实现面包屑导航、JSON-LD、aria-current、移动端样式和测试。

用 Claude Code 实现可访问面包屑导航

面包屑导航看起来只是标题上方的一行小链接,但在真实站点里,它同时影响站点结构、内部链接、可访问性、结构化数据和移动端阅读体验。实现得不好时,页面看起来仍然正常,屏幕阅读器却无法识别当前页,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 BreadcrumbListBreadcrumbList 表示一串网页,position 用来明确顺序。如果希望 Google 搜索结果使用面包屑,还要参考 Google Search Central 的面包屑结构化数据

决策要确定什么常见失败
标签使用标题、分类名还是字典覆盖直接显示 claude-code-breadcrumb-navigation
URLHTML 用相对路径,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.tsxsiteUrl 传入不带末尾斜杠的域名,例如 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 文件里接收 itemssiteUrl,用 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 和商业导线。

#Claude Code #面包屑 #navigation #结构化数据 #UX
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。