How to Build Dynamic Breadcrumbs in Remix

Piotr Kulpinski - Mar 29 - - Dev Community

In Remix, building dynamic breadcrumbs that reflect your route hierarchy is straightforward. This tutorial will guide you through leveraging the useMatches and handle capabilities to achieve this.

We'll also cover how to add schema metadata to your breadcrumbs for better SEO and social sharing.

The Basics

Breadcrumbs are a navigation aid that helps users understand their current location within a website. They typically appear horizontally at the top of a page and show the hierarchy of the current page in relation to the site structure.

Here's an example of what breadcrumbs code could look like:

<ol itemscope itemtype="https://schema.org/BreadcrumbList">
  <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
    <a href="https://example.com/" itemprop="item">
      <span itemprop="name">Home</span>
    </a>
    <meta itemprop="position" content="1" />
  </li>

  <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
    <a href="https://example.com/posts" itemprop="item">
      <span itemprop="name">Posts</span>
    </a>
    <meta itemprop="position" content="2" />
  </li>

  <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
    <a href="https://example.com/posts/slug" itemprop="item">
      <span itemprop="name">Post Title</span>
    </a>
    <meta itemprop="position" content="3" />
  </li>
</ol>
Enter fullscreen mode Exit fullscreen mode

Now, let's see how to build dynamic breadcrumbs in Remix.

Defining the Breadcrumbs Components

Let's start by creating the necessary component that we'll later use to render the breadcrumbs.

We'll need a Breadcrumbs component to render the list of breadcrumbs and a BreadcrumbsItem component to render each breadcrumb item.

Start by defining a wrapper component that will list all of the breadcrumbs.

// app/components/Breadcrumbs.tsx
import { UIMatch, useMatches } from "@remix-run/react"
import { Fragment, HTMLAttributes } from "react"

type BreadcrumbMatch = UIMatch<
  Record<string, unknown>,
  { breadcrumb: (data?: unknown) => JSX.Element }
>

export const Breadcrumbs = ({ ...props }: HTMLAttributes<HTMLElement>) => {
  const matches = (useMatches() as unknown as BreadcrumbMatch[]).filter(
    ({ handle }) => handle?.breadcrumb
  )

  return (
    <ol
      itemScope
      itemType="https://schema.org/BreadcrumbList"
      className="flex flex-wrap items-center gap-2.5"
      {...props}
    >
      {matches.map(({ handle, data }, i) => (
        <Fragment key={i}>
          <li
            className="contents"
            itemProp="itemListElement"
            itemScope
            itemType="https://schema.org/ListItem"
          >
            {i > 0 && <span className="text-sm">/</span>}
            {handle.breadcrumb(data)}
            <meta itemProp="position" content={`${i + 1}`} />
          </li>
        </Fragment>
      ))}
    </ol>
  )
}
Enter fullscreen mode Exit fullscreen mode

In the example above, we're using the useMatches hook to get the current route matches. We then filter the matches to only include those that have a breadcrumb handle. We will define this handle in the routes where we want to include a breadcrumb. We then iterate over the matches and render the breadcrumb component for each match.

You may have noticed that we're also passing a data prop to the handle.breadcrumb function. This is because the breadcrumb handle can accept data from the route loader. We'll see how to pass data to the breadcrumb handle in the next section.

Next, let's define the BreadcrumbsItem component that we'll use to render each breadcrumb item.

// app/components/BreadcrumbsItem.tsx
import { Link } from "@remix-run/react"
import { HTMLAttributes } from "react"

export const BreadcrumbsItem = ({ children, ...props }: HTMLAttributes<HTMLElement>) => {
  return (
    <Link to={props.href} itemProp="item" {...props}>
      <span itemProp="name">{children}</span>
    </Link>
  )
}
Enter fullscreen mode Exit fullscreen mode

Now that we have our components, let's define the breadcrumbs in our routes.

Defining the Breadcrumbs handles in the Routes

In the routes where you want to include breadcrumbs, you need to define a breadcrumb handle that will render the breadcrumb item.

For example, let's define the following routes:

  • / - Home
  • /posts - Posts
  • /posts/$slug - Post Details
// app/routes/index.tsx
import { BreadcrumbsItem } from "~/components/BreadcrumbsItem"

export const handle = {
  breadcrumb: () => <BreadcrumbsItem to="/">Home</BreadcrumbsItem>,
}

export default function Index() {
  return <h1>Home</h1>
}
Enter fullscreen mode Exit fullscreen mode
// app/routes/posts/index.tsx
import { BreadcrumbsItem } from "~/components/BreadcrumbsItem"

export const handle = {
  breadcrumb: () => <BreadcrumbsItem to="/posts">Posts</BreadcrumbsItem>,
}

export default function Posts() {
  return <h1>Posts</h1>
}
Enter fullscreen mode Exit fullscreen mode
// app/routes/posts.$slug.tsx
import { json, type LoaderFunctionArgs } from "@remix-run/node"
import { useLoaderData } from "@remix-run/react"
import { BreadcrumbsItem } from "~/components/BreadcrumbsItem"

export const handle = {
  breadcrumb: (data: { title: string; slug: string }) => (
    <BreadcrumbsItem to={`/posts/${data.slug}`}>{data.title}</BreadcrumbsItem>
  ),
}

export const loader = ({ params }: LoaderFunctionArgs) => {
  const post = {
    title: "Example Post",
    slug: "example-post",
  }

  return json(post)
}

export default function Post() {
  const { title } = useLoaderData<typeof loader>()

  return <h1>{title}</h1>
}
Enter fullscreen mode Exit fullscreen mode

In the last route, we're using the loader function to fetch the post data. We then pass this data to the breadcrumb handle to render the breadcrumb item. This allows us to dynamically generate the breadcrumb item based on the post data.

Conclusion

Hopefully, this guide has helped you understand how to implement breadcrumbs in your Remix application.

If you're interested to see how breadcrumbs can be implemented in a real-world application, you can check out the OpenAlternative project, or check out the source code.

If you have any questions or need further clarification, feel free to reach out to me on Twitter.

. . .