Reputation: 834
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
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
Reputation: 1148
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 be layout.tsx
or layout.ts
.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