Next.js 15.3.2 - Keep SSR for server DOM wrapped by client/Suspense (without force-dynamic)?
Unanswered
Black Catbird posted this in #help-forum
Black CatbirdOP
In dev, my server-rendered DOM (e.g. headings) appears in initial HTML. In prod (next build && next start), those elements disappear from page source when they’re rendered inside a Client wrapper and/or a Suspense boundary - unless I set
Not all routes use
E.g.
What’s the recommended pattern to preserve SSR of SEO-critical DOM (keep it in initial HTML + maintain cache HIT with
Is the guidance to render SSR-critical DOM before any client/Suspense boundary, and defer the rest under Suspense (PPR-style)? Any other caveats or segment-splitting patterns to avoid this prod-only behavior?
Env: Next.js 15.3.2, App Router,
dynamic='force-dynamic'
, which I don’t want (kills Full Route Cache).Not all routes use
searchParams
, so this isn’t only about query strings. It’s about server DOM nested under client and/or Suspense.E.g.
// page.tsx (Server)
export const revalidate = 86400; // keep caching
export default async function Page() {
const data = await getData(); // resolves at build / ISR
const title = data.title; // SEO-critical
return (
<>
{/* This shows in dev, but not in prod when nested below */}
<ClientShell> {/* "use client" */}
<Suspense fallback={<Skeleton/>}>
<ServerSection> {/* Server Component */}
<h1>{title}</h1> {/* disappears from initial HTML in prod */}
</ServerSection>
</Suspense>
</ClientShell>
</>
);
}
What’s the recommended pattern to preserve SSR of SEO-critical DOM (keep it in initial HTML + maintain cache HIT with
dynamic='auto'
) while still using client providers and/or Suspense deeper in the tree?Is the guidance to render SSR-critical DOM before any client/Suspense boundary, and defer the rest under Suspense (PPR-style)? Any other caveats or segment-splitting patterns to avoid this prod-only behavior?
Env: Next.js 15.3.2, App Router,
dynamic='auto'
, route-level revalidate
in place.