AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

Next.Js App Router: Not-Found.Tsx GenerateMetadata Lost After Hydration

When a page calls notFound(), the server-rendered HTML includes correct metadata from not-found.tsx, but after client hydration the metadata is replaced by the root layout's metadata because the Flight payload lacks the fallback's metadata. This causes incorrect document head and can harm SEO.

mediumConfidence 75%Next.jsAffected V16.3.0-Canary.86

Origin Analysis

During SSR, the runtime notFound() changes the effective render tree to the not-found component, and Next.js correctly resolves its metadata for the initial HTML. However, the Flight payload serialized for hydration does not include this fallback metadata; it only contains metadata from the original page/layout. The client uses this incomplete metadata state to update the head after hydration, overwriting the correct server-rendered values.
1. Create a Next.js App Router page that calls notFound() immediately. 2. Export a generateMetadata function from the adjacent not-found.tsx with custom title/description. 3. Load the page and inspect the SSR HTML response (correct custom metadata). 4. Inspect the document head after client hydration; metadata reverts to root layout defaults.

Fixing Code Block

// components/NotfoundMetadataSync.tsx 'use client'; import { useEffect } from 'react'; interface NotfoundMetadataSyncProps { title?: string; description?: string; } export default function NotfoundMetadataSync({ title, description }: NotfoundMetadataSyncProps) { useEffect(() => { if (title) { document.title = title; } if (description) { let meta = document.querySelector('meta[name="description"]'); if (!meta) { meta = document.createElement('meta'); meta.name = 'description'; document.head.appendChild(meta); } meta.setAttribute('content', description); } }, [title, description]); return null; } // app/not-found.tsx import NotfoundMetadataSync from '@/components/NotfoundMetadataSync'; export async function generateMetadata() { return { title: 'Not Found Title', description: 'Not Found Description' }; } export default function NotFound() { return ( <> <NotfoundMetadataSync title="Not Found Title" description="Not Found Description" /> <h1>Not Found</h1> </> ); }
Because the Flight payload does not include the metadata from the not-found boundary, we force the client to re-apply the correct metadata after hydration using a client component that directly manipulates document.title and meta[name=description]. This mirrors the server-rendered values and prevents the root layout metadata from persisting after hydration.

Edge Case Audit

This is a temporary workaround and only handles title and description; other metadata tags such as Open Graph or Twitter cards will still be lost after hydration. It bypasses Next.js metadata management and could conflict with future client-side metadata updates or framework patches. For rollback, remove the NotfoundMetadataSync component and its usage from not-found.tsx. Once Next.js resolves the underlying Flight payload issue, this workaround should be removed.

Ecosystem Topology