← All posts

Dynamic Rendering and Prerendering: When They Still Make Sense in 2026

VisibilityIQ

A real-estate marketplace built entirely as a client-rendered single-page app had solved its Google problem years ago with dynamic rendering: detect Googlebot, hand it a prerendered HTML snapshot, hand everyone else the JavaScript app. It worked. Google indexed everything. Then the team noticed their listings were absent from AI search answers — Perplexity, ChatGPT search, and the AI Overview citations that were starting to drive referral traffic. The reason was structural. Their dynamic-rendering rule keyed on a hardcoded list of search-engine user agents. The AI crawlers were not on the list, so they got the JavaScript app — which they could not execute — and saw an empty shell. The workaround that solved indexing in 2019 had become the reason their content was invisible to the crawlers that mattered in 2026.

This is the state of dynamic rendering and prerendering today: Google considers it deprecated, the original problem it solved (Googlebot not rendering JavaScript) is gone, and yet a new population of non-rendering crawlers has appeared that makes the underlying concern relevant again. The right answer is not “dynamic rendering is dead” and it is not “you still need dynamic rendering” — it is a decision tree that depends on which crawlers you care about, what your rendering architecture already is, and how much divergence risk you can tolerate.

Why Google Walked Away From It

Dynamic rendering means serving different responses to different user agents at the same URL: a prerendered static HTML snapshot to crawlers, the live JavaScript application to human browsers. Google recommended it as a stopgap when Googlebot’s JavaScript rendering was slow, resource-constrained, and unreliable. By 2022 Google had reclassified it as a workaround it no longer recommends, for reasons that are operational more than punitive.

Googlebot renders JavaScript competently now. Its rendering uses an evergreen Chromium, executes modern JavaScript, and — with the caveats covered in the render-budget discussion — produces a rendered DOM that reflects what a browser would show. The premise of dynamic rendering, that Googlebot cannot see JavaScript-rendered content, no longer holds for Google. Serving a separate prerendered path specifically for Googlebot is solving a problem Google does not have.

The cost of keeping it is real. Dynamic rendering is a second rendering pipeline running in parallel with the user-facing one. It must be kept in sync: when the app changes, the prerendered snapshots must regenerate, and any drift between what the prerenderer produces and what users get is a divergence bug. Worse, divergence in the wrong direction is cloaking — if the prerendered version shown to crawlers contains content or signals the user version does not, that is the definition of serving different content to search engines, and it carries penalty risk. The user-agent detection itself is fragile: a hardcoded crawler list goes stale, exactly as the real-estate marketplace’s did, the moment a new crawler appears.

Google’s recommended alternative is to remove the branching entirely: render the same HTML for everyone via server-side rendering or static generation. One response, one path, no detection, no divergence. For a site that can adopt SSR or SSG, that is unambiguously the better architecture.

The AI Crawler Problem Brings the Concern Back

The complication is that “Googlebot renders JavaScript” is not the same as “crawlers render JavaScript.” A large and growing set of crawlers do not execute JavaScript at all, and for them a client-rendered page is an empty shell.

The major AI crawlers — GPTBot (OpenAI’s training and search crawler), ClaudeBot, PerplexityBot, Google-Extended, OAI-SearchBot, and others — predominantly fetch raw HTML and do not run a full JavaScript rendering pass the way Googlebot does. They retrieve the server response and parse what is there. If the meaningful content is injected by client-side JavaScript and absent from the initial HTML, these crawlers see the <div id="root"></div> and little else. The content is invisible to them not because of a directive or a block, but because they never execute the code that produces it.

This matters more every quarter because AI-driven discovery is becoming a real referral and visibility surface. Citations in AI Overviews, answers in Perplexity, and references in ChatGPT search increasingly send traffic and shape brand visibility. A page that ranks fine in Google but is invisible to GPTBot is invisible in the answers those systems generate. The problem dynamic rendering was built for — content invisible to non-rendering crawlers — has reappeared, just with a different set of crawlers as the audience.

This does not, however, argue for reviving user-agent-keyed dynamic rendering. The same fragility applies: you would be maintaining a crawler allowlist that goes stale, and you would be serving different content to AI crawlers than to users, with the same divergence and cloaking risks. The lesson from the real-estate marketplace is precisely that hardcoded user-agent lists fail to anticipate the next crawler. The robust answer is the one that requires no list at all: put the content in the HTML the server sends, so that no crawler — Google, Bing, GPTBot, ClaudeBot, or whatever launches next year — has to execute JavaScript to read it.

The Real Decision Tree

The choice in 2026 is not “dynamic rendering or not.” It is a sequence of questions that lead to one of a few architectures.

Question 1: Can the content be server-rendered or statically generated? If yes — and for most content sites, marketing sites, ecommerce, listings, and documentation, the answer is yes — do that. SSR or SSG serves identical HTML to every user agent, eliminates the rendering question entirely, and is the most robust answer for Googlebot, AI crawlers, and users simultaneously. This should be the default and the destination. Modern frameworks (Next.js, Astro, Nuxt, SvelteKit) make SSR/SSG the path of least resistance for new builds, and the migration target for existing SPAs.

Question 2: Is the app a legacy client-rendered SPA that cannot be economically migrated to SSR in the near term? This is the real constraint that keeps prerendering alive. A large, mature SPA with deep client-side state may not be reasonable to convert quickly. For these, prerendering at the edge is the pragmatic interim: generate static HTML snapshots of the app’s routes and serve them to all crawlers — not via brittle user-agent detection, but ideally as the served HTML for everyone, with client-side hydration taking over for interactivity. Edge prerendering (rendering the app in a headless browser at the CDN layer and caching the result) gets you SSR-equivalent HTML without rebuilding the app, and serving it to all visitors avoids the cloaking and stale-allowlist problems of crawler-only delivery.

Question 3: If you must branch by user agent, who is on the list? If operational reality forces true dynamic rendering — prerendered to crawlers, app to users — then the crawler list is the liability, and it must include the AI crawlers, not just the search engines. GPTBot, ClaudeBot, PerplexityBot, Google-Extended, OAI-SearchBot, and Bytespider belong on it alongside Googlebot and Bingbot. But understand this for what it is: a workaround with a maintenance burden and a stale-list failure mode baked in. It is the answer of last resort, justified only when SSR and edge prerendering are both genuinely off the table, and it should carry a plan to migrate off it.

The through-line of all three answers is the same principle: the content should be in the server-rendered HTML. SSR achieves it directly. Edge prerendering achieves it without an app rewrite. Dynamic rendering achieves it only for the crawlers you remembered to list, which is why it ranks last. Every step down this tree trades robustness for the constraint that forced it, and the goal is always to climb back toward “everyone gets the same rendered HTML.”

What “Edge Prerendering” Looks Like in Practice

Edge prerendering deserves more than a passing mention because it is the answer that resolves the most common real constraint — a legacy SPA you cannot rebuild — without inheriting dynamic rendering’s failure modes. The mechanism: at the CDN edge, a worker intercepts the request, runs the SPA through a headless browser (or fetches a cached snapshot it has already rendered), waits for the app to render and the network to settle, and returns the resulting static HTML. Critically, it returns that HTML to everyone, not just to crawlers. Human browsers receive fully-formed HTML and then hydrate it for interactivity, exactly as an SSR framework would deliver. There is no user-agent branching, so there is no allowlist to maintain and no cloaking surface.

The operational concerns are caching and freshness. Rendering a page in a headless browser is expensive — hundreds of milliseconds to seconds — so you cannot do it per request at scale. The snapshot gets cached at the edge with a TTL tuned to how often the content changes: long for stable marketing and documentation routes, short for inventory that updates frequently. Cache invalidation on content change is the discipline that keeps the prerendered HTML from drifting stale. Done well, edge prerendering gives a client-rendered app SSR-equivalent crawlability with a fraction of the engineering investment a full SSR migration demands, which is why it is the right interim answer rather than a compromise to apologize for.

The one trap to avoid: do not let the prerendered snapshot and the live app diverge in content. If the snapshot is generated from a different data source, a different build, or a stale cache while the live app shows something else, you reintroduce the divergence problem under a new name. The snapshot must be a faithful render of the same app the same data feeds — a cached version of the truth, not a parallel version of it.

Knowing which architecture you actually have — and whether it is failing a specific crawler — requires comparing what each user agent receives. A technical-SEO platform that simulates multiple user agents and captures what each one sees can surface the exact failure the real-estate marketplace hit: a page that renders full content for Googlebot but returns an empty shell to GPTBot or ClaudeBot, a dynamic-rendering rule whose crawler allowlist has gone stale, or a prerendered snapshot that has drifted out of sync with the user-facing version into cloaking territory. Flagging the pages where content is present for one crawler and absent for another — with the specific user agent and the missing content named — turns “are the AI crawlers seeing our content?” from an untestable worry into a concrete, per-page answer.

Frequently asked questions

Is dynamic rendering still recommended by Google?
No. Google has classified dynamic rendering as a workaround, not a long-term recommendation, since 2022. Its position is that Googlebot renders JavaScript well enough that serving a separate prerendered version specifically for crawlers is unnecessary complexity for most sites, and it recommends server-side rendering or static generation instead. That said, 'not recommended' is not 'broken' — dynamic rendering still works and Google does not penalize it as cloaking when the prerendered content matches what users get. The reason to move away from it is operational: it's a parallel rendering path to maintain, a source of divergence bugs, and it solves a problem (Googlebot can't render JS) that no longer exists for Google.
Do AI crawlers like GPTBot and ClaudeBot run JavaScript?
Predominantly no. The major AI crawlers — GPTBot, ClaudeBot, PerplexityBot, and others that fetch content for training and for answer generation — largely retrieve raw HTML and do not execute JavaScript the way Googlebot's renderer does. A JavaScript-dependent page that renders fine for Googlebot can appear nearly empty to these crawlers, which see only the initial HTML shell. As AI-driven discovery surfaces grow in importance, this resurrects the exact problem dynamic rendering was built to solve — content invisible to non-rendering crawlers — for a new and growing audience. The robust answer is the same one that serves Googlebot best: put the content in the server-rendered HTML so no crawler has to execute JavaScript to read it.
What's the difference between dynamic rendering, prerendering, and SSR?
Server-side rendering (SSR) generates HTML on the server for every request and serves the same rendered HTML to everyone — users and crawlers alike. Static generation (SSG) prebuilds HTML at build time. Prerendering generates a static HTML snapshot of a JavaScript app, often via a headless browser, and serves that snapshot. Dynamic rendering specifically means detecting the user agent and serving prerendered HTML to crawlers while serving the client-side JavaScript app to human users — two different responses for the same URL based on who's asking. SSR and SSG serve one consistent response to everyone, which is why they're preferred: no user-agent branching, no divergence risk, no separate path to maintain.