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
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
So after seeing where your components are located I can say that:
ServerLayout
component is technically not a Next.js Layout because the filename must belayout.tsx
orlayout.ts
.ServerLayout
component is neither a page or a layout technically, thegenerateMetadata
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 parameterslug
, the layout should be insideapp/[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?
We can keep
ServerLayout
insidecomponents/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 thegenerateMetadata()
function and a passthrough component that returns children directly.Basically something like:
app/[slug]/layout.tsx
:This way, any page that is a descendent of
app/[slug]
will share thisgenerateMetadata
definition.