Skip to main content
Back to Blog
Tutorials
2 min read
November 9, 2024

How to Add Breadcrumb Navigation to Your Next.js App

Build automatic breadcrumb navigation in Next.js using the App Router. Includes schema markup for rich snippets.

Ryel Banfield

Founder & Lead Developer

Breadcrumbs improve navigation, reduce bounce rates, and can appear as rich snippets in Google search results.

Step 1: Create the Breadcrumb Component

// components/Breadcrumbs.tsx
"use client";

import { usePathname } from "next/navigation";
import Link from "next/link";

interface BreadcrumbItem {
  label: string;
  href: string;
}

const LABEL_MAP: Record<string, string> = {
  services: "Services",
  blog: "Blog",
  about: "About",
  contact: "Contact",
  pricing: "Pricing",
  industries: "Industries",
  locations: "Locations",
};

function generateBreadcrumbs(pathname: string): BreadcrumbItem[] {
  const segments = pathname.split("/").filter(Boolean);
  return segments.map((segment, index) => ({
    label:
      LABEL_MAP[segment] ||
      segment
        .replace(/-/g, " ")
        .replace(/\b\w/g, (c) => c.toUpperCase()),
    href: "/" + segments.slice(0, index + 1).join("/"),
  }));
}

export function Breadcrumbs() {
  const pathname = usePathname();
  const breadcrumbs = generateBreadcrumbs(pathname);

  if (breadcrumbs.length === 0) return null;

  return (
    <nav aria-label="Breadcrumb" className="mb-6">
      <ol className="flex flex-wrap items-center gap-1.5 text-sm text-gray-500 dark:text-gray-400">
        <li>
          <Link
            href="/"
            className="transition-colors hover:text-gray-900 dark:hover:text-white"
          >
            Home
          </Link>
        </li>
        {breadcrumbs.map((crumb, index) => (
          <li key={crumb.href} className="flex items-center gap-1.5">
            <span aria-hidden="true">/</span>
            {index === breadcrumbs.length - 1 ? (
              <span className="font-medium text-gray-900 dark:text-white" aria-current="page">
                {crumb.label}
              </span>
            ) : (
              <Link
                href={crumb.href}
                className="transition-colors hover:text-gray-900 dark:hover:text-white"
              >
                {crumb.label}
              </Link>
            )}
          </li>
        ))}
      </ol>
    </nav>
  );
}

Step 2: Add JSON-LD Schema Markup

// components/BreadcrumbSchema.tsx
interface Props {
  items: { name: string; url: string }[];
}

export function BreadcrumbSchema({ items }: Props) {
  const schema = {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    itemListElement: items.map((item, index) => ({
      "@type": "ListItem",
      position: index + 1,
      name: item.name,
      item: item.url,
    })),
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  );
}

Step 3: Use in Layout

// app/(site)/layout.tsx
import { Breadcrumbs } from "@/components/Breadcrumbs";

export default function SiteLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="mx-auto max-w-7xl px-4 py-8">
      <Breadcrumbs />
      {children}
    </div>
  );
}

Step 4: Server Component Version

For better SEO, create a server component that generates breadcrumbs from page metadata:

// components/ServerBreadcrumbs.tsx
import Link from "next/link";

interface BreadcrumbItem {
  label: string;
  href: string;
}

export function ServerBreadcrumbs({ items }: { items: BreadcrumbItem[] }) {
  const allItems = [{ label: "Home", href: "/" }, ...items];

  return (
    <>
      <nav aria-label="Breadcrumb" className="mb-6">
        <ol className="flex flex-wrap items-center gap-1.5 text-sm text-gray-500">
          {allItems.map((item, i) => (
            <li key={item.href} className="flex items-center gap-1.5">
              {i > 0 && <span aria-hidden="true">/</span>}
              {i === allItems.length - 1 ? (
                <span className="font-medium text-gray-900 dark:text-white" aria-current="page">
                  {item.label}
                </span>
              ) : (
                <Link
                  href={item.href}
                  className="hover:text-gray-900 dark:hover:text-white"
                >
                  {item.label}
                </Link>
              )}
            </li>
          ))}
        </ol>
      </nav>
      <BreadcrumbSchema
        items={allItems.map((item) => ({
          name: item.label,
          url: `https://rcbsoftware.com${item.href}`,
        }))}
      />
    </>
  );
}

Step 5: Use on a Blog Post Page

// app/(site)/blog/[slug]/page.tsx
import { ServerBreadcrumbs } from "@/components/ServerBreadcrumbs";

export default async function BlogPostPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = getPostBySlug(slug);

  return (
    <article>
      <ServerBreadcrumbs
        items={[
          { label: "Blog", href: "/blog" },
          { label: post.title, href: `/blog/${slug}` },
        ]}
      />
      <h1>{post.title}</h1>
      {/* post content */}
    </article>
  );
}

Step 6: Style Variants

// Chevron separator variant
<span aria-hidden="true">
  <ChevronRight className="h-3.5 w-3.5" />
</span>

// Truncated long labels
<span className="max-w-[150px] truncate">
  {crumb.label}
</span>

// With icons
<li className="flex items-center gap-1.5">
  <HomeIcon className="h-3.5 w-3.5" />
  <Link href="/">Home</Link>
</li>

Need Better Site Navigation?

We design websites with intuitive navigation, breadcrumbs, and structured data that help users and search engines. Contact us for a free consultation.

breadcrumbsnavigationNext.jsSEOtutorial

Ready to Start Your Project?

RCB Software builds world-class websites and applications for businesses worldwide.

Get in Touch

Related Articles