ReactHustle

How to Create Dynamic NextJS Breadcrumbs Component

Jasser Mark Arioste

Jasser Mark Arioste

How to Create Dynamic NextJS Breadcrumbs Component

In this tutorial, you'll learn how to create a dynamic data-driven breadcrumbs component in NextJS with TailwindCSS and Typescript. 

Introduction #

There are many good guides out there when creating breadcrumbs component but creating hard-coded components seems simple at first but it gets a bit complex once dynamic data is involved.

In this tutorial, I'll show you some insights and what I think about when creating data-driven components such as breadcrumbs.

When initially implementing breadcrumbs, you might just use the router.pathname and extract the data from there. But what if you want a different text than the actual path, like the image below for example?

How do we actually implement this in NextJS? If you have a similar problem, I'll share my insights on what to think about when building these kinds of components.

Questions to Ask? #

First, we should ask ourselves to understand more about the breadcrumbs component itself:

  1. Do the breadcrumbs have a different text from the actual path? In this tutorial, YES they are different so we can't use NextJS router to automatically generate the breadcrumbs.
  2. Does each page have a breadcrumb or only selected pages? Nope, not each page has breadcrumbs.
  3. Where does the data for the breadcrumbs come from? We'll assume that the data comes from a CMS or backend. For example, if you're building an online course app, the course usually has the data to which category it belongs. If you're building a blog, each post usually has a category and subcategories for it in the backend.
  4. Should it be server-side rendered for better SEO? Yes.
  5. Do I have to use TailwindCSS for styling? Nope, you can use your own preferred method. It's just my preferred method to use TailwindCSS for styling.

Tutorial Objectives #

What we'll be doing:

  1. Define the breadcrumbs data type. It's just an array with text and url properties in each item.
  2. Create a standalone react <Breadcrumbs/> functional component that accepts the data we defined above.
  3. Provide data using getStaticProps or getServerSideProps

Final Output #

Step 0: Project Setup #

All right, with that out of the way, let's start with the project setup.

If you'd like to follow along from scratch, I created a GitHub starter template with TailwindCSS already installed. But if you'd like to work on your existing project, and don't like to use tailwind, you may skip this step.

All right, run the command:

npx create-next-app -e https://github.com/jmarioste/next-tailwind-starter-2 next-js-breadcrumbs-tutorial

This is going to create a new NextJS app in a project called next-js-breadcrumbs-tutorial.

Next, run the command to start the local server:

cd next-js-breadcrumbs-tutorial && yarn dev

Step 1 - Defining the Data #

First, let's define what our breadcrumbs data looks like when we get it from the server. In some cases, like if you're using GraphQL, this data is already defined in the schema. Here, we'll just start with a simple structure. Create the file components/breadcrumbs/Breadcrumbs.ts

// components/breadcrumbs/Breadcrumbs.ts
import { ReactNode } from "react";
// defining the Props
export type CrumbItem = {
  label: ReactNode; // e.g., Python
  path: string; // e.g., /development/programming-languages/python
};
export type BreadcrumbsProps = {
  items: CrumbItem[];
};
12345678910

Here, we're just defining a simple CrumbItem that has a label and a path, multiple of these items will define the breadcrumbs trail. For the label, we use a ReactNode type so that we can pass react components or images if we want to, and allow maximum flexibility.

Step 2 - Creating the Breadcrumbs Component #

The next step is to create the Breadcrumbs component itself. Examine the code below:

import Link from "next/link";
// ...omitted for brevity
// components/breadcrumbs/Breadcrumbs.ts
const Breadcrumbs = ({ items }: BreadcrumbsProps) => {
  return (
    <div className="flex gap-2 items-start">
      {items.map((crumb, i) => {
        const isLastItem = i === items.length - 1;
        if (!isLastItem) {
          return (
            <>
              <Link
                href={crumb.path}
                key={i}
                className="text-indigo-500 hover:text-indigo-400 hover:underline"
              >
                {crumb.label}
              </Link>
              {/* separator */}
              <span> / </span>
            </>
          );
        } else {
          return crumb.label;
        }
      })}
    </div>
  );
};
export default Breadcrumbs;
123456789101112131415161718192021222324252627282930

We iterate over the breadcrumb items and if it is not the last item, we use a next/link component. If it's the last item, we simply render the text. We then sprinkle some Tailwind CSS classes to make it look good.

Step 3 - Usage #

The component is a dump and independent component and we can now just pass in the data like below for example:

// pages/index.tsx
import type { NextPage } from "next";
import Breadcrumbs from "../components/breadcrumbs/Breadcrumbs";
import Image from "next/image";
const Home: NextPage = () => {
  return (
    <div className="container mx-auto">
      <h1 className="my-2"> Welcome To NextJS Tailwind Starter</h1>
      <div className="my-2">
        <Breadcrumbs
          items={[
            {
              label: (
                <Image src="/home.svg" height={24} width={24} alt="home icon" />
              ),
              path: "/",
            },
            {
              label: "Development",
              path: "/courses/development",
            },
            {
              label: "Programming Languages",
              path: "/courses/development/programming-languages",
            },
            {
              label: "Python",
              path: "/topic/python",
            },
          ]}
        />

        <Breadcrumbs
          items={[
            {
              label: "Home",
              path: "/",
            },
            {
              label: "Development",
              path: "/courses/development",
            },
            {
              label: "Programming Languages",
              path: "/courses/development/programming-languages",
            },
            {
              label: "Python",
              path: "/topic/python",
            },
          ]}
        />
      </div>
    </div>
  );
};
export default Home;
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657

In the first usage, we used the next/image component to render an SVG for the home icon. In the second usage, we used pure text. We can see that the component is very flexible in this regard.

And the output is like this:

Next JS Breadcrumbs Output

Step 4 - Providing Dynamic Data to the Breadcrumbs #

In the previous step, we use static data to render the breadcrumbs. Next, let's provide some sort of dynamic data to simulate data received from a CMS backend which is a very common pattern.

Suppose, you have the path /course/[slug].tsx where it displays the breadcrumbs component on the page. All right, let's create the route, create the file pages/course/[slug].tsx:

// pages/course/[slug].tsx
import { NextPage } from "next";
import Breadcrumbs, { CrumbItem } from "../../components/breadcrumbs/Breadcrumbs";
type BreadcrumbData = { // data returned by server
  text: string;
  url: string;
};
// data for this page that we're expecting from backend
type Props = {
  breadcrumbs: BreadcrumbData[];
  courseTitle: string;
};
const CoursePage: NextPage<Props> = (props) => {
  const breadCrumbsData: CrumbItem[] = props.breadcrumbs.map((c) => {
    return {
      label: c.text,
      path: c.url,
    };
  });
  return (
    <div className="container mx-auto">
      <div className="my-4">
        <Breadcrumbs items={breadCrumbsData} />
        <h1 className="text-4xl my-2">{props.courseTitle}</h1>
      </div>
    </div>
  );
};

export default CoursePage;
123456789101112131415161718192021222324252627282930

Here we created a page that displays breadcrumbs and the course title. We map over the breadcrumbs and transform them since it doesn't match BreadcrumbsProps.

Next, let's add some custom data, we'll simulate backend calls to the database using getServerSideProps. First, create the file data/db.json and copy the code below:

[
  {
    "slug": "learn-python",
    "courseTitle": "Learn Python: Python for Beginners",
    "breadcrumbs": [
      {
        "text": "Home",
        "url": "/"
      },
      {
        "text": "Development",
        "url": "/courses/development"
      },
      {
        "text": "Programming Languages",
        "url": "/courses/development/programming-languages"
      },
      {
        "text": "Python",
        "url": "/course/python"
      }
    ]
  }
]
123456789101112131415161718192021222324

We'll pretend that this is our database that contains all the content for our pages.

Next, let's modify course/[slug].tsx and add getStaticProps:

import { GetServerSideProps, NextPage } from "next";
import Breadcrumbs, {
  CrumbItem,
} from "../../components/breadcrumbs/Breadcrumbs";
import db from "../../data/db.json";
// ...omitted for brevity
export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
  const slug = ctx.params?.slug;
  // simulate a call to the backend server here to get the data
  const data = db.find((page) => page.slug === slug);
  if (!data) {
    return {
      notFound: true,
    };
  }
  return {
    props: {
      breadcrumbs: data.breadcrumbs,
      courseTitle: data.courseTitle,
    },
  };
};
12345678910111213141516171819202122

Now, when we navigate to localhost:3000/course/learn-python, we get the following result:

Next JS breadcrumbs output slug

That's it!

Full Code and Demo #

The full code can be accessed at GitHub: NextJS Breadcrumbs Tutorial

The demo can be accessed at Stackblitz: NextJS Breadcrumbs Tutorial

Conclusion #

You learned how to create a simple and independent dynamic breadcrumbs component in NextJS using TailwindCSS and Typescript. We provided data to the breadcrumbs component by using a hard-coded list or dynamic data from getServerSideProps from an imaginary backend server.

If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.

Credits: Image by ThuyHaBich from Pixabay

Share this post!

Related Posts