Tips & Tricks (Diperbarui: 2/6/2026)

Breadcrumb Aksesibel dengan Claude Code

Implementasi breadcrumb dengan Claude Code, JSON-LD, aria-current, CSS mobile, dan test React/Astro.

Breadcrumb Aksesibel dengan Claude Code

Breadcrumb terlihat seperti baris kecil di atas judul, tetapi di production ia menyentuh struktur situs, internal link, aksesibilitas, structured data, dan layout mobile. Implementasi yang lemah bisa terlihat rapi, tetapi screen reader tidak memahami halaman aktif, JSON-LD berisi URL relatif, atau judul panjang merusak tampilan mobile.

Saat saya mencoba pola ini di template ClaudeCodeLab, draft pertama Claude Code membuat Home > Blog > Title, tetapi melewatkan aria-current, memakai URL relatif di JSON-LD, buruk di mobile, dan menampilkan slug mentah. Claude Code bisa memperbaikinya jika kontrak implementasi diberikan sejak awal.

Panduan ini menunjukkan cara meminta Claude Code membuat dan mereview breadcrumb aksesibel untuk React, situs bergaya Next.js, dan Astro. Baca juga optimasi SEO, aksesibilitas, Astro, dan React.

Kontrak Desain

Breadcrumb bukan pengganti tombol back. Ia menunjukkan hierarki, memberi jalan ke halaman induk, dan membantu mesin pencari memahami relasi halaman. WAI-ARIA APG Breadcrumb Pattern menjelaskan penggunaan navigation landmark dengan label dan aria-current="page" untuk halaman aktif.

Structured data mengikuti schema.org BreadcrumbList. Setiap item perlu position agar urutan jelas. Untuk tampilan Google Search, gunakan dokumentasi resmi breadcrumb structured data.

KeputusanYang harus ditentukanKegagalan umum
LabelJudul, kategori, atau kamus labelSlug tampil mentah
URLLink HTML dan URL absolut JSON-LDStructured data berisi path relatif
Halaman aktifLink atau teks di item terakhirStatus hanya lewat warna
MobileTampilkan semua atau ringkas tengahBreadcrumb memakan banyak baris
LocalePrefix dan label lokalBahasa tercampur

Prompt untuk Claude Code

Implementasikan breadcrumb untuk situs React/Next.js atau Astro.
Syarat:
- Terima items sebagai { label: string; href: string }[].
- Tambahkan aria-current="page" pada item terakhir.
- Gunakan nav aria-label="Breadcrumb".
- Separator diberi aria-hidden="true".
- Buat JSON-LD BreadcrumbList dari array items yang sama.
- Ubah URL JSON-LD menjadi absolut menggunakan siteUrl.
- Tambahkan helper untuk membuat items dari pathname.
- Format slug agar mudah dibaca dan dukung override dari kamus label.
- Di mobile, sembunyikan item tengah dan tetap buat halaman aktif terbaca.
- Tambahkan test Vitest untuk root, nested path, label lokal, dan query string.
- Setelah selesai, list checklist aksesibilitas dan structured data.

Untuk perubahan besar, lakukan review kedua dengan checklist workflow review.

Komponen React

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) }}
      />
    </>
  );
}

Helper Route

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;
}
import { Breadcrumb } from "@/components/Breadcrumb";
import { buildBreadcrumbs } from "@/lib/breadcrumbs";

const siteUrl = "https://claudecodelab.com";

export default async function ArticlePage() {
  const pathname = "/id/blog/claude-code-breadcrumb-navigation";
  const labels = {
    "/": "Beranda",
    "/id": "Indonesia",
    "/id/blog": "Artikel",
    "/id/blog/claude-code-breadcrumb-navigation":
      "Breadcrumb Aksesibel dengan Claude Code",
  };
  const items = buildBreadcrumbs(pathname, labels);

  return (
    <main>
      <Breadcrumb items={items} siteUrl={siteUrl} ariaLabel="Breadcrumb" />
      <h1>Breadcrumb Aksesibel dengan Claude Code</h1>
    </main>
  );
}

Di Astro, gunakan bentuk data yang sama, ambil path dari Astro.url.pathname, lalu output JSON-LD dengan set:html={JSON.stringify(jsonLd)}.

CSS Mobile

.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; }
}

Test

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("/id/blog/claude-code-breadcrumb-navigation", {
        "/": "Beranda",
        "/id": "Indonesia",
        "/id/blog": "Artikel",
        "/id/blog/claude-code-breadcrumb-navigation": "Breadcrumb aksesibel",
      }),
    ).toEqual([
      { label: "Beranda", href: "/" },
      { label: "Indonesia", href: "/id" },
      { label: "Artikel", href: "/id/blog" },
      { label: "Breadcrumb aksesibel", href: "/id/blog/claude-code-breadcrumb-navigation" },
    ]);
  });
});

Untuk E2E, periksa nav[aria-label], satu aria-current="page", JSON-LD yang bisa diparse, URL absolut, dan layout mobile tanpa overlap. Lanjutkan dengan Playwright testing.

Use Case dan Pitfall

Use case utama: blog/dokumentasi, halaman ecommerce atau training berbayar, layar admin SaaS yang dalam, dan situs multi-bahasa. Breadcrumb membantu pembaca kembali ke topik induk dan memperkuat internal link.

Pitfall umum: halaman aktif hanya dibedakan warna, JSON-LD memakai URL relatif, slug mentah tampil sebagai label, mobile wrap terlalu panjang, dan UI/JSON-LD memakai dua array berbeda. Sebelum publish, cek aria-label, aria-current, separator tersembunyi, BreadcrumbList, position, URL absolut, konsistensi UI/JSON-LD, mobile, locale, dan validasi Search Console.

CTA dan Verifikasi

Di ClaudeCodeLab, breadcrumb membantu mengarahkan pembaca ke cheatsheet gratis, produk, dan training/konsultasi Claude Code. Saya memverifikasi contoh dengan memisahkan komponen React, helper route, dan test Vitest. Keputusan paling berguna adalah menghasilkan UI dan JSON-LD dari items yang sama; draft dengan dua array mudah tidak sinkron setelah kategori diganti.

Ringkasan

Breadcrumb aksesibel bukan sekadar separator antar link. Beri Claude Code label, route, aria-current, JSON-LD, URL absolut, perilaku mobile, dan test sekaligus, lalu review dengan sumber resmi sebelum publish.

#Claude Code #breadcrumb #navigation #structured data #UX
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.