skip to Main Content

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 };

2

Answers


  1. 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

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search