I am using Next.js App Router (app/) with dynamic routes and generating metadata based on API responses.
However, my metadata does NOT appear in the SSR <head> when I check “View Page Source”.
Instead, metadata is either missing , or appears inside the <body> after hydration.
Project Context
Route: /app/(propertyPages)/(SeoPages)/[...slugs]/page.tsx
Metadata is generated using generateMetadata()
Metadata depends on API calls (example: getPageBySlugOne, validateSlugsOne, etc.)
I am using:
export const revalidate = 86400; // ISR 24h
export const dynamicParams = true; // Enable on-demand generation
API functions run inside generateMetadata()
The Problem
Even though metadata is returned correctly from generateMetadata, it does not show inside <head> in SSR HTML.
What I actually see in “View Page Source”:
Only global metadata from layout.tsx
NO title
NO meta description
NOTHING from generateMetadata()
Instead, Next.js injects metadata after hydration inside the <body>
This is the HTML output I uploaded:
(uploaded exact file here: random.txt )
All metadata is missing from <head> in the raw server HTML.
How can I force metadata to be rendered server-side inside <head> even when it comes from an API?
3. Does ISR + dynamicParams still make generateMetadata() behave dynamically?
4. Do I need to use cache(), dynamic = "force-static", or { next: { revalidate }} inside fetch() to fix this?
@harpreet-3932 @amyegan
I hope you can help me out with this issue
amyegan
(Amy Egan)
November 20, 2025, 8:58pm
6
The Next.js team have said that this is intentional when streaming metadata is involved
opened 08:34PM - 16 May 25 UTC
closed 09:20PM - 19 May 25 UTC
Metadata
locked
### Link to the code that reproduces this issue
https://github.com/theroot79/my… -meta-test-app
### To Reproduce
npx create-next-app@latest my-meta-test-app
Need to install the following packages:
create-next-app@15.3.2
Ok to proceed? (y) y
✔ Would you like to use TypeScript? … **No** / Yes
✔ Would you like to use ESLint? … No / **Yes**
✔ Would you like to use Tailwind CSS? … **No** / Yes
✔ Would you like your code inside a `src/` directory? … **No** / Yes
✔ Would you like to use App Router? (recommended) … No / **Yes**
✔ Would you like to use Turbopack for `next dev`? … **No** / Yes
✔ Would you like to customize the import alias (`@/*` by default)? … No / **Yes**
✔ What import alias would you like configured? … **@/***
### Current vs. Expected behavior
Expected behavior:
All <meta> tags should render inside <head>, not <body>.
Actual behavior:
On first load, several <meta> tags (e.g. description, robots, etc.) appear inside <body> instead of <head>, breaking SEO and causing hydration mismatches.
### Provide environment information
```bash
npx --no-install next info
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 24.4.0: Fri Apr 11 18:32:50 PDT 2025; root:xnu-11417.101.15~117/RELEASE_ARM64_T6041
Available memory (MB): 36864
Available CPU cores: 14
Binaries:
Node: 23.11.0
npm: 10.9.2
Yarn: 1.22.22
pnpm: 10.11.0
Relevant Packages:
next: 15.3.2 // Latest available version is detected (15.3.2).
eslint-config-next: 15.3.2
react: 19.1.0
react-dom: 19.1.0
typescript: 5.8.3
Next.js Config:
output: N/A
```
### Which area(s) are affected? (Select all that apply)
Metadata
### Which stage(s) are affected? (Select all that apply)
next dev (local)
### Additional context
<img width="1155" alt="Image" src="https://github.com/user-attachments/assets/493a941d-381c-4f35-a067-f294e2b8af28" />
whenever metadata resolution would potentially block rendering the page, we instead defer it and stream it into the page body. Browsers are still able to interpret the title tag properly regardless of where it’s rendered in the DOM
I hope that helps!
1 Like
farzigalib
(farzigalib)
November 21, 2025, 8:08am
7
Recently I worked on same scenario, check out the below code in your page.tsx
/app/(propertyPages)/(SeoPages)/[...slugs]/page.tsx
interface PageProps {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const { slug } = await params;
const workPreview = await getWorkPreviewAPI({ type: 'slug', id: slug });
if (!workPreview.success) {
return {};
}
const {
metaTitle,
metaDescription,
metaKeywords,
metaOGImg,
metaAltText,
bannerImage,
} = workPreview.blogData;
return {
title: metaTitle,
description: metaDescription,
keywords: metaKeywords,
openGraph: {
title: metaTitle,
description: metaDescription,
images: {
url: getImageUrl(metaOGImg ?? bannerImage),
alt: metaAltText ?? '',
},
},
twitter: {
title: metaTitle,
description: metaDescription,
images: {
url: getImageUrl(metaOGImg ?? bannerImage),
alt: metaAltText ?? '',
},
card: 'summary_large_image',
},
};
}
On local you might be not get expected result but if make it like it will work as expected.
2 Likes
pawlean
(Pauline P. Narvas)
November 21, 2025, 10:41am
8
@farzigalib Thanks for sharing what worked for you
1 Like
thanks for replying , but i am doiing the same
farzigalib
(farzigalib)
November 21, 2025, 5:31pm
10
I would help you if you provide more context of you code, specially layout.tsx and […slug]/page.tsx
farzigalib
(farzigalib)
November 21, 2025, 5:38pm
11
I implemented the same in one of my project you can check it here
So basically /case-studies/[slug] is dynamic and every page have it own metadata
actually problem is in only this url , else dynamic route are woring fine as expectd
farzigalib
(farzigalib)
November 24, 2025, 7:15am
13
Create layout.tsx for (SeoPages) & (propertyPages) and then it will work. Try once.
1 Like