Jason Biondo
Jason Biondo

Reputation: 834

How to Get Slug in Server-Side Layout in Next.js Component?

I am trying to pass a slug to a server-side function in my Next.js layout component. My slug is returning undefined, and I need to find a way to correctly grab and utilize the slug on the server side within the layout file. Despite using getServerSideProps to fetch the slug in my page component, it seems the slug is not making its way to the layout component. I suspect there might be an issue with how I'm attempting to pass the slug as a prop or perhaps with the overall structure of my code. Is there a proper method to capture the slug on the server side specifically in a layout file in Next.js? Here is my code:

import { Metadata } from "next";
import { fetchMetadata } from "@/lib/fetch-meta-data";
import { lora, poppins } from "@/app/fonts";

type Params = {
  slug: string;
};

type SearchParams = {
  [key: string]: string | string[] | undefined;
};

type Props = {
  params: Params;
  searchParams: SearchParams;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = params;

  console.log(params, "Log params in generateMetadata");
  const pageMetadata = await fetchMetadata(slug);

  const defaultTitle = "Default App Name";
  const defaultDescription =
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
  const defaultImageUrl = `/images/default.jpg`;
  const pageTitle = pageMetadata?.title
    ? `${pageMetadata.title} | App Name`
    : defaultTitle;

  return {
    title: pageTitle,
    description: pageMetadata?.description || defaultDescription,
    keywords: pageMetadata?.keywords || "default, keywords",
    applicationName: "App Name",
    authors: [{ name: "App Name" }],
    publisher: "App Name",
    formatDetection: {
      email: false,
      address: false,
      telephone: false,
    },
    openGraph: {
      title: pageMetadata?.title || defaultTitle,
      description: pageMetadata?.description || defaultDescription,
      url: `/path/${slug}`,
      siteName: "App Name",
      images: [
        {
          url: pageMetadata?.imageUrl || defaultImageUrl,
          width: 1200,
          height: 630,
        },
      ],
      locale: "en_US",
      type: "website",
    },
    twitter: {
      card: "summary_large_image",
      title: pageMetadata?.title || defaultTitle,
      description: pageMetadata?.description || defaultDescription,
      images: [pageMetadata?.imageUrl || defaultImageUrl],
    },
    robots: {
      index: true,
      follow: true,
    },
  };
}

export default function ServerLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html
      lang="en-US"
      suppressHydrationWarning
      className={`${poppins.variable} ${lora.variable}`}
    >
      <body suppressHydrationWarning>{children}</body>
    </html>
  );
}

This file is my server-layout inside of my components/layouts folder. This file gets called in my layout.tsx file inside of my app directory.

This is my app layout code:

// app/layout.tsx

import ServerLayout, {
  generateMetadata,
} from "@/components/layouts/main/server-layout";
import ClientLayout from "@/components/layouts/main/client-layout";

export default function AppLayout({ children }: { children: React.ReactNode }) {
  return (
    <ServerLayout>
      <ClientLayout>{children}</ClientLayout>
    </ServerLayout>
  );
}

export { generateMetadata };

Upvotes: 0

Views: 1202

Answers (3)

Jason Biondo
Jason Biondo

Reputation: 834

For anyone that is looking to solve this, the best way to do it is to add the pathname as a header using your middleware so that then you can get the header value inside of your layout components.

Upvotes: 0

moonstar-x
moonstar-x

Reputation: 1148

So after seeing where your components are located I can say that:

  • Your ServerLayout component is technically not a Next.js Layout because the filename must be layout.tsx or layout.ts.
  • Since your ServerLayout component is neither a page or a layout technically, the generateMetadata function does not get called anywhere.
  • ServerLayout has no way to identify what the dynamic path slug is called because its file is not inside a dynamic path folder. In your case, since you're naming this parameter slug, the layout should be inside app/[slug].

Keep in mind that route resolution is done by folder structure. You can also leverage route groups to group up pages that will use your layouts without adding anything onto the route.


Given this, how about keeping a folder structure similar to this?

.
├── app/
│   ├── [slug]/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── layout.tsx
│   └── page.tsx
└── components/
    └── layouts/
        └── main/
            ├── server-layout.tsx
            └── client-layout.tsx

We can keep ServerLayout inside components/layouts/main/server-layout.tsx so long as we treat it as a regular React component.

Now, inside the file app/[slug]/layout.tsx you can include the generateMetadata() function and a passthrough component that returns children directly.

Basically something like:

app/[slug]/layout.tsx:

export const generateMetadata = () => {
  return //my metadata;
}

export default PassthroughLayout = ({ children }: { children: React.ReactNode }) => {
  return children;
}

This way, any page that is a descendent of app/[slug] will share this generateMetadata definition.

Upvotes: 0

Hmmmmm
Hmmmmm

Reputation: 277

actually layout components in Next.js do not directly receive the same props as page components.

You can check the solution to using slugs in layout component here

Upvotes: 0

Related Questions