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.

Dynamic rendering request flow An incoming request is classified as bot or user; bots receive a cached prerendered HTML snapshot while users receive the client-side app bundle. Incoming request Bot user agent? Cached prerendered HTML snapshot yes Client-side app bundle (live) no
Dynamic rendering branches on the request: bots get a pre-executed HTML snapshot, users get the live SPA. Keep the two in sync to avoid cloaking.

Step-by-step fix

  1. 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.

  2. 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.

  3. 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;
    }
  4. 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

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.

← Back to Prerendering & SSR Strategies