How to Create Dynamic NextJS Breadcrumbs Component
Jasser Mark Arioste
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:
- 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. - Does each page have a breadcrumb or only selected pages? Nope, not each page has breadcrumbs.
- 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.
- Should it be server-side rendered for better SEO? Yes.
- 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:
- Define the breadcrumbs data type. It's just an array with
text
andurl
properties in each item. - Create a standalone react
<Breadcrumbs/>
functional component that accepts the data we defined above. - Provide data using
getStaticProps
orgetServerSideProps
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:
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:
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