Dynamic Rendering & Bot Prerendering
Prerendering executes your single-page app in a headless browser and caches the resulting HTML so that a crawler receives fully populated markup instead of an empty shell. Dynamic rendering is the variant that serves that snapshot specifically to bots while users continue to get the live app. Both let a client-rendered application become crawlable without migrating to server-side rendering, which is why they remain a pragmatic option inside the broader prerendering and SSR strategies toolkit — even though Google now frames dynamic rendering as a workaround rather than a destination.
Prerequisites
- A CSR app whose public routes are deterministic from the URL (no auth required to see indexable content).
- A prerendering mechanism: a build-time prerenderer, a self-hosted service, or edge middleware that can run headless Chromium.
- The ability to branch on the request — by user agent for dynamic rendering, or to prerender for everyone.
- A canonical list of routes to prerender (usually derived from your sitemap).
How it breaks
Two failure shapes dominate. First, the snapshot and the live app drift: a deploy changes the client app but the prerender cache is not invalidated, so crawlers index stale titles, prices, or structured data. Second, the bot-detection branch misfires — a new crawler user agent is not matched, so it receives the empty SPA shell and indexes nothing.
Step-by-step fix
-
Prefer prerendering for everyone over user-agent branching. Serving the same prerendered HTML to users and bots eliminates cloaking risk and the divergence problem. Only fall back to user-agent detection when the app cannot tolerate a static first paint.
-
Generate snapshots from your sitemap. Drive the prerenderer from the canonical URL list so coverage matches what you submit to search engines. The React prerendering walkthrough shows a build-time setup.
-
Invalidate the cache on deploy and on data change. Tie snapshot regeneration to your deploy pipeline and to content updates so the cache never outlives the live content.
// Edge middleware: serve a fresh-enough snapshot, regenerate when stale const MAX_AGE = 60 * 60; // 1 hour async function handle(request, cache) { const snap = await cache.get(request.url); if (snap && snap.age < MAX_AGE) return snap.html; const html = await prerender(request.url); // headless render await cache.put(request.url, { html, age: 0 }); return html; } -
Verify metadata survives the snapshot. The prerendered HTML must contain the final title, canonical, and JSON-LD, not the pre-update defaults — align with avoiding metadata hydration pitfalls.
Gotchas & edge cases
- Snapshot timeout. If the prerenderer captures the DOM before async data resolves, it caches a half-rendered page. Wait for a render-complete signal, not a fixed timeout.
- Cloaking drift. Any content the bot snapshot includes but users never see (or vice versa) risks a cloaking penalty; audit parity regularly.
- Infinite or query-dependent routes. Prerendering every filter combination explodes the cache; canonicalize filtered views instead of snapshotting them all.
- Relying on dynamic rendering long-term. Google recommends migrating to SSR or hydration over time; treat dynamic rendering as a bridge. See whether dynamic rendering is still recommended.
Validation checklist
Performance & crawl-budget notes
Because a snapshot is plain HTML, bots index it in the first wave and never spend render-queue budget on the route — the same indexing-velocity win as SSR, achieved without rebuilding the app. The cost moves to your prerender infrastructure: each cache miss runs a headless render, so cache hit rate and invalidation strategy determine whether prerendering is cheap or expensive at scale.
Go deeper
- Set up prerendering for a React SPA — a concrete build-time and middleware setup.
- Is dynamic rendering still recommended by Google? — what changed in Google’s guidance and what to do about it.
Frequently Asked Questions
Is dynamic rendering considered cloaking? Not if the prerendered HTML matches the content users see. Cloaking is serving materially different content to crawlers than to users. Dynamic rendering serves the same content in a pre-executed form, so it is allowed — but it drifts toward cloaking if the snapshot and the live app diverge.
Do I still need prerendering if Google renders JavaScript? Google renders JavaScript, but rendering is queued and capped by an execution budget, and other crawlers and social scrapers render little or no JavaScript. Prerendering guarantees content and metadata in the first response for every bot, which is why it remains useful for CSR apps that cannot move to SSR.
Related
- CSR, SSG, SSR & ISR Rendering Strategies — when to invest in SSR instead of prerendering.
- How Googlebot’s Rendering Pipeline works — why the render queue makes snapshots valuable.
- SEO Audit Workflows for SPAs — verify snapshot parity with real tools.
← Back to Prerendering & SSR Strategies