Next.js Discord

Discord Forum

Dynamic breadcrumbs & RootLayout, how should I combine those two?

Answered
TheMelonAssassin posted this in #help-forum
Open in Discord
Avatar
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body
        className={cn(
          "min-h-screen bg-background font-sans antialiased",
          fontSans.variable
        )}
      >
        <SidebarProvider>
          <AppSidebar />
          <SidebarInset>
            <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
              <div className="flex items-center gap-2 px-4">
                <SidebarTrigger className="-ml-1" />
                <Separator orientation="vertical" className="mr-2 h-4" />
                <Breadcrumb>
                  <BreadcrumbList>
                    <BreadcrumbItem className="hidden md:block">
                      <BreadcrumbLink href="#">
                        Building Your Application
                      </BreadcrumbLink>
                    </BreadcrumbItem>
                    <BreadcrumbSeparator className="hidden md:block" />
                    <BreadcrumbItem>
                      <BreadcrumbPage>Data Fetching</BreadcrumbPage>
                    </BreadcrumbItem>
                  </BreadcrumbList>
                </Breadcrumb>
              </div>
            </header>
            <main>{children}</main>
          </SidebarInset>
        </SidebarProvider>
      </body>
    </html>
  );
}


This is my RootLayout. As you can see I've added a sidebar from shadcn to it so that it can be used across all pages.

I also like the breadcrumbs, but I'm struggling to understand on how I can make them more dynamic.

Say I navigated to /events

import { getAllEvents } from "@/actions/events";
import { columns } from "@/components/events/columns";
import { EventTable } from "@/components/events/event-table";

export default async function Page() {
  const events = await getAllEvents();

  return <EventTable columns={columns} data={events} />;
}


I'd like to define the breadcrumbs here and then display them in the rootlayout
Answered by TheMelonAssassin
Alright I got there, just had to switch around some stuff

layout.tsx

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body
        className={cn(
          "min-h-screen bg-background font-sans antialiased",
          fontSans.variable
        )}
      >
        <SidebarProvider>
          <AppSidebar />
          <SidebarInset>
            <main>{children}</main>
          </SidebarInset>
        </SidebarProvider>
      </body>
    </html>
  );
}


breadcrumb-wrapper.tsx

export type BreadcrumbWrapperProps = {
  breadcrumbs: {
    label: string;
    href?: string; // Optional for the current page
  }[];
};

export function BreadCrumbWrapper({ breadcrumbs }: BreadcrumbWrapperProps) {
  return (
    <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
      <div className="flex items-center gap-2 px-4">
        <SidebarTrigger className="-ml-1" />
        <Separator orientation="vertical" className="mr-2 h-4" />
        <Breadcrumb>
          <BreadcrumbList>
            {breadcrumbs.map(({ label, href }, index) => (
              <>
                <BreadcrumbItem key={label}>
                  {href ? (
                    <BreadcrumbLink href={href}>{label}</BreadcrumbLink>
                  ) : (
                    <BreadcrumbPage>{label}</BreadcrumbPage>
                  )}
                </BreadcrumbItem>
                {index < breadcrumbs.length - 1 && (
                  <BreadcrumbSeparator className="hidden md:block" />
                )}
              </>
            ))}
          </BreadcrumbList>
        </Breadcrumb>
      </div>
    </header>
  );
}


page.tsx

const breadcrumbs = [{ label: "Dashboard", href: "/" }, { label: "Events" }];

export default async function Page() {
  const events = await getAllEvents();

  return (
    <>
      <BreadCrumbWrapper breadcrumbs={breadcrumbs} />
      <div className="mx-4 mb-4 rounded-3xl bg-white p-10 shadow-lg">
        <EventTable columns={columns} data={events} />
      </div>
    </>
  );
}
View full answer

7 Replies

Avatar
Antillean Palm Swift
I would suggest making BreadCrumbs a separate component and use ‘usePathname’ - https://nextjs.org/docs/app/api-reference/functions/use-pathname

and get the pathname and show it in the breadcrumbs.
Avatar
@Antillean Palm Swift I would suggest making BreadCrumbs a separate component and use ‘usePathname’ - https://nextjs.org/docs/app/api-reference/functions/use-pathname and get the pathname and show it in the breadcrumbs.
Avatar
Yeah I checked this out, but for instance when I'm at URL events/:id I'd rather not show the id but the name of the event.

I know how to do all that with a wrapper and passing some values in after fetching the data from the db.

It's moreso how can I keep the wrapper in the rootlayout and have full control over what I pass into it
Avatar
@James4u It's better to have BreadCumbs component as a client component
Avatar
I copied this setup straight from shad/cn blocks, I'm messing around with NextJS for the first time so quite a way to go
It's the combination with the <SidebarProvider>, <AppSidebar />, <SidebarInset>, <SidebarTrigger> that's giving me issues.
Let me rework the layout
Avatar
Alright I got there, just had to switch around some stuff

layout.tsx

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body
        className={cn(
          "min-h-screen bg-background font-sans antialiased",
          fontSans.variable
        )}
      >
        <SidebarProvider>
          <AppSidebar />
          <SidebarInset>
            <main>{children}</main>
          </SidebarInset>
        </SidebarProvider>
      </body>
    </html>
  );
}


breadcrumb-wrapper.tsx

export type BreadcrumbWrapperProps = {
  breadcrumbs: {
    label: string;
    href?: string; // Optional for the current page
  }[];
};

export function BreadCrumbWrapper({ breadcrumbs }: BreadcrumbWrapperProps) {
  return (
    <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
      <div className="flex items-center gap-2 px-4">
        <SidebarTrigger className="-ml-1" />
        <Separator orientation="vertical" className="mr-2 h-4" />
        <Breadcrumb>
          <BreadcrumbList>
            {breadcrumbs.map(({ label, href }, index) => (
              <>
                <BreadcrumbItem key={label}>
                  {href ? (
                    <BreadcrumbLink href={href}>{label}</BreadcrumbLink>
                  ) : (
                    <BreadcrumbPage>{label}</BreadcrumbPage>
                  )}
                </BreadcrumbItem>
                {index < breadcrumbs.length - 1 && (
                  <BreadcrumbSeparator className="hidden md:block" />
                )}
              </>
            ))}
          </BreadcrumbList>
        </Breadcrumb>
      </div>
    </header>
  );
}


page.tsx

const breadcrumbs = [{ label: "Dashboard", href: "/" }, { label: "Events" }];

export default async function Page() {
  const events = await getAllEvents();

  return (
    <>
      <BreadCrumbWrapper breadcrumbs={breadcrumbs} />
      <div className="mx-4 mb-4 rounded-3xl bg-white p-10 shadow-lg">
        <EventTable columns={columns} data={events} />
      </div>
    </>
  );
}
Answer