Skip to content

SEOPart of: JavaScript SEO

Dynamic rendering and prerendering: when they help and when to move on

Dynamic rendering serves crawlers a prerendered HTML snapshot while visitors get the JavaScript app. Google now calls it a workaround, not a long-term solution. How it works, when it is still worth it, the risks, and how build-time prerendering differs.

getReport teamUpdated 26 Sept 202610 min read

Dynamic rendering means detecting crawlers by their User-Agent and serving them a prerendered HTML snapshot of the page, while normal visitors get the client-side JavaScript app. It was Google's recommended fix for JavaScript-heavy sites for a few years, but since 2022 Google's documentation calls it "a workaround and not a long-term solution" and recommends server-side rendering, static rendering or hydration instead. It still has a place as a bridge on a client-rendered site that cannot move yet. This guide explains how it works, when it helps, the risks, and how it differs from prerendering at build time. It is part of our guide to JavaScript SEO and making script-built sites indexable.

Quick answer

  • Dynamic rendering = two versions of every page: a snapshot for bots, the JavaScript app for people. A headless browser or a prerendering service makes the snapshots.
  • Google calls it a workaround. Its dynamic rendering page recommends server-side rendering, static rendering or hydration.
  • It is not cloaking as long as the snapshot shows the same content visitors see after JavaScript runs.
  • The risks are operational: stale snapshots, bots you forgot to list, wrong status codes, and a second system to debug.
  • Build-time prerendering (static generation) is different and recommended. Everyone, bots and people, gets the same HTML.
  • Check what a crawler and a browser receive with the free JavaScript SEO check.

What dynamic rendering is

A client-rendered site sends an almost empty HTML file and builds the page in the browser. Crawlers that do not run JavaScript see nothing, and Google sees the content only after its later rendering step. Dynamic rendering puts a switch in front of the site:

  1. A request arrives at the web server, CDN or middleware.
  2. The switch checks the User-Agent against a list of bots: Googlebot, Bingbot, link preview bots, AI crawlers.
  3. Bots are sent to a renderer: a headless Chromium that loads the page, waits for the scripts and returns the finished HTML. The result is usually cached.
  4. Everyone else gets the normal JavaScript app.

The renderer can be a hosted prerendering service, which you connect through middleware or a CDN rule, or a self-hosted headless browser built on Puppeteer or Playwright. Google's own open-source renderer for this job, Rendertron, was deprecated and its repository archived in October 2022; its notice says dynamic rendering is not a recommended approach.

Why Google calls it a workaround

Google's documentation gives the reason directly: dynamic rendering adds complexity and resource requirements, and there are better solutions. In practice the problems are these.

Two systems to keep in sync. Every bug now has two places to live. A page can look perfect in the browser while its snapshot is broken, blank or out of date, and nobody on the team sees the snapshot unless they go looking.

Stale content. Snapshots are cached to keep the renderer affordable. A price change, a sold-out product or a corrected article reaches crawlers only when the cache refreshes.

Bots you did not list. The switch only helps bots it recognises. New AI crawlers, a new link preview bot or a new Google crawler get the empty app until someone adds them to the list.

Status codes. The renderer must pass through the right status: 404 for missing products, 301 for moved pages. Many setups return 200 with a rendered "not found" view, which creates soft 404s at scale.

Timeouts and partial renders. If an API is slow, the renderer may snapshot a loading state, and that is what gets indexed.

Cost. Rendering every page for every bot takes CPU, and hosted services typically charge by the number of pages rendered.

None of this makes dynamic rendering harmful when it works. It makes it fragile, and all the effort goes into keeping a second copy of the site correct instead of making the first copy right.

Is dynamic rendering cloaking?

No, as long as the content matches. Google's documentation says Googlebot generally does not treat dynamic rendering as cloaking when it produces similar content. Serving bots something different from what people see, such as extra text, extra links or a different topic, is cloaking and breaks Google's spam policies.

"Similar" means the snapshot equals what a visitor sees after the scripts run. Inlining the product description that the app loads from an API is fine; adding a keyword paragraph that visitors never see is not.

Note

Automated audits compare what a bot and a visitor receive without running JavaScript, and a dynamically rendered site looks very different in that comparison: the bot gets full HTML, the visitor an empty shell. getReport's hacked-site checks fetch the page as a visitor and as Googlebot, so on such a site the cloaking finding below can fire. Read its evidence: if the extra words are your own content, the ones visitors see after JavaScript, the difference is your dynamic rendering, not a hack.

When dynamic rendering still makes sense

Use it as a bridge, with an end date:

  • A large client-rendered app whose public pages cannot move to server rendering this quarter, where content is missing from Google or from link previews today.
  • A legacy stack (an old AngularJS app, for example) that will be replaced, where investing in server rendering for the old code makes no sense.
  • Link previews and AI crawlers for a client-rendered site, while the migration is planned.

Do not use it for a new project. Every current framework can render on the server or at build time from day one, which is simpler than rendering twice.

Prerendering at build time is different

"Prerendering" is used for two different things, which causes a lot of confusion:

Dynamic rendering (prerender for bots)Build-time prerendering (static generation)
Who gets the HTMLOnly recognised botsEveryone
When it is madeOn request, then cachedOnce, at deploy time
Needs User-Agent detectionYesNo
Google's viewA workaroundRecommended ("static rendering")
Risk of drift between versionsYesNo, there is one version

Build-time prerendering renders each route to an HTML file during the build and serves that file to every visitor and crawler, with JavaScript hydrating it afterwards. React Router's prerender option, Angular's RenderMode.Prerender, Nuxt's routeRules with prerender: true, Next.js static rendering and dedicated static site generators all do this. It has none of the drift problems because there is only one version.

If your "prerendering service" serves its snapshots to everyone, it is closer to static generation. If it serves them only to bots, it is dynamic rendering. Our comparison of client-side and server-side rendering puts both next to SSR and CSR.

If you run dynamic rendering: a checklist

  1. List every bot that matters, not only Googlebot and Bingbot: link preview bots (Facebook, LinkedIn, Slack, WhatsApp, X, Discord, Apple) and the AI crawlers you allow. The guide to AI crawlers and robots.txt lists the current user agents.
  2. Pass status codes and redirects through from the app to the snapshot, including 404, 410 and 301.
  3. Refresh the cache when content changes, from your CMS or deploy pipeline, not only on a timer.
  4. Wait for the content, not a fixed delay. Have the app signal when it is ready, so the renderer does not snapshot a spinner.
  5. Keep head tags in the snapshot: title, description, canonical, robots and structured data.
  6. Monitor the snapshot, not only the app. Fetch key URLs with a bot User-Agent after every deploy.
  7. Plan the exit. Move public routes to server rendering or static generation one template at a time, and remove the switch for each.

How to check a dynamically rendered site

Two views need checking: what bots get and what people get.

The check fetches the URL as raw HTML with getReport's own crawler User-Agent, then renders it in Chromium, and compares the two field by field. If your switch does not recognise getReport's bot, the raw column shows the client-side shell, which is also what any unlisted crawler receives. That is a useful test in itself.

To see the snapshot, request the page with a bot User-Agent and compare it with the rendered page:

Shell
# What Googlebot is served (the snapshot)
curl -s -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
  https://example.com/products/blue-shoe/ > bot.html

# What a browser gets before JavaScript (the shell)
curl -s https://example.com/products/blue-shoe/ > browser.html

The snapshot should contain the same title, text, links and structured data as the rendered page, and nothing more. In Google Search Console, URL Inspection → View crawled page shows what Googlebot actually received. The AI crawler check shows whether AI bots are allowed to fetch the page at all.

Common mistakes

  • Treating it as permanent. Snapshots drift, bot lists age, and the migration never starts.
  • Only listing Googlebot, which leaves link previews and AI crawlers with an empty page.
  • Returning 200 for everything from the renderer.
  • Snapshotting too early, so crawlers index loading placeholders.
  • Adding bot-only content, which turns a workaround into cloaking.
  • Confusing it with static generation and choosing the harder option by mistake.

Questions people ask

Is dynamic rendering still a good idea?

Only as a temporary bridge. Google still processes dynamically rendered pages, but since 2022 its documentation calls dynamic rendering a workaround and not a long-term solution, and recommends server-side rendering, static rendering or hydration instead. It is not cloaking when crawlers and visitors see the same content, but snapshots go stale and new crawlers go unrecognised. Use it while you move public pages to server or static rendering.

Is a prerendering service good for SEO?

It can fix indexing for a client-rendered site in the short term, because crawlers receive complete HTML. The cost is a second system: snapshots can go stale, return the wrong status code or miss bots that are not on the list. If the service serves the same HTML to everyone, it behaves like static generation, which Google recommends. If it serves snapshots only to bots, it is dynamic rendering, which Google calls a workaround.

What is the difference between dynamic rendering and server-side rendering?

Who gets the HTML. Server-side rendering builds the complete page on the server for every request and sends it to everyone, visitors and crawlers alike. Dynamic rendering keeps the client-side app for visitors and serves a separately rendered snapshot only to requests whose User-Agent looks like a bot. SSR has one version of each page; dynamic rendering has two, which is why it is harder to keep correct.

Check your site before and after Check