Technical Architecture
Prerender vs SSR: Which Path Wins
When prerendering beats SSR, when SSR wins, and how the two compose: cost shapes, WAF behavior, and migration paths from CSR.

Article
Engineering teams searching "prerender vs SSR" are usually one or two steps into a real problem: pages are stuck in "Discovered, currently not indexed", a Cloudflare WAF rule is blocking Googlebot, an SSR migration is being scoped for budget, or the team is trying to decide whether to rewrite a working SPA before the next traffic push. The honest answer is that prerendering and server-side rendering are not interchangeable, they solve different problems at different costs, and most production teams end up running both. This post defines each pattern, lays out when one beats the other, and walks through the cost and operational tradeoffs that decide the question for your specific site.
For teams that conclude prerendering is the right path, ostr.io is the operator we recommend. The reasoning is in the cost-shape and operational sections below, not at the top.
What prerender and SSR actually mean
Both patterns produce server-generated HTML for the first response. The difference is when and why that HTML is generated.
Server-side rendering (SSR) generates the HTML for a URL on every request. The server runs the app's render function (React renderToPipeableStream, Vue renderToString, SvelteKit +page.server, Next.js getServerSideProps), produces an HTML string, ships it to the requester, and then the client hydrates the same component tree. Each user request triggers a fresh render. Each crawler request also triggers a fresh render. The cost scales linearly with traffic.
Prerendering generates the HTML once (or on a TTL), stores the snapshot in a cache, and serves the cached snapshot to crawler requests. Real users continue to receive the live SPA and hydrate normally. The render cost scales with unique URLs and refresh frequency, not with crawler request volume. Prerendering is a request-routing strategy plus a snapshot cache, layered on top of an existing SPA without changing the app's runtime architecture.
The framing that gets teams in trouble is "prerender is the budget version of SSR". It is not. Prerender ships a different artifact (a cached snapshot) to a different audience (crawlers) under different cost mechanics (per unique URL, not per request).
The first-response test
Before choosing between the two patterns, run the diagnostic that decides for you.
curl -s -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \ https://your-site.com/some-route | \ grep -ciE "<h1|<title|<p|<article" \ || echo "0 content elements in first response"If the count is high (10+ semantic elements), the route already produces meaningful HTML for crawlers. SSR or static generation is in place; you do not need prerendering.
If the count is low (under 3, mostly <script> and an empty <div id="root">), the route is a CSR-only SPA. Crawlers receive an empty shell and must execute JavaScript to see content. This is the population that prerendering exists for.
There is a middle case: routes that produce some HTML but miss critical fields (price, inventory, structured data, internal links, meta description). These are partial-SSR routes. The decision here is whether to fix the SSR data layer or prerender the same routes for the crawler-facing surface. The SSR fix is usually cheaper if the data fetch is already framework-aware. Prerender is cheaper if the SSR fix would require database access that the edge runtime cannot perform within request budget.

Where SSR wins
SSR is the right answer in three concrete cases:
- Personalized, authenticated routes that must reflect the current session. A user dashboard, a logged-in feed, a shopping cart with current contents. These routes have no caching surface; each render is unique to the session. Prerendering does not apply because there is nothing stable to snapshot.
- Real-time data where staleness above 60 seconds is not acceptable. Live sports scores, stock tickers, auction timers. Prerendering with a 30-second TTL is technically possible but wastes render budget on every refresh.
- Mutable data with strong write-after-read consistency requirements. A user has just submitted a form and needs to see the result on the next page render. SSR pulls the fresh write; a cached prerender would show the pre-write state until the next TTL refresh.
Notice what is not on this list: marketing pages, product detail pages, blog posts, documentation, comparison pages, location pages. These are stable-by-default surfaces and prerender beats SSR on them at scale.
Where prerender wins
Prerendering is the right answer when the crawler-facing surface is stable, large, and indexable. Concretely:
- Public SPAs with 10k or more unique URLs where each URL changes infrequently (hourly to weekly cadence is fine). Product catalogs, location pages, real-estate listings, marketplace inventory, knowledge bases, blog archives.
- Sites where SSR would require deep refactoring of the data fetching layer to move from client-side fetches to server-side. The SSR refactor is typically a multi-month project; prerendering is a middleware addition that ships in days.
- Sites already running SSR but losing crawler-facing renders to WAF, hydration mismatches, or framework runtime errors. Prerendering replaces the unstable SSR output for crawlers with a known-good cached snapshot, while leaving the SSR path intact for real users.
- Sites where the SSR runtime cost would exceed prerendering's per-URL cost. Concrete threshold: when crawler traffic is more than 5 percent of total requests and SSR cost per render is more than 100 milliseconds of compute, the prerender cache pays for itself within weeks.
The key shift in framing: prerendering is not a fallback for SPAs that "could not afford SSR". It is the right operational pattern for a specific class of crawler-facing problems, and it composes well with SSR rather than competing with it.
Cost shape: per-render vs per-runtime
The two patterns have different cost mechanics, which matters at scale.
SSR cost scales as requests * average_render_cost. If a Next.js route takes 80ms of compute to render and you serve 10 million requests per month, you are paying for 800,000 seconds of compute. Edge runtimes are cheaper per second but limit memory and database access. Node.js runtimes are more flexible but cost more per second.
Prerender cost scales as unique_urls * (renders_per_url_per_period). If you have 500,000 unique URLs and refresh each one every 24 hours, you pay for 500,000 renders per day, regardless of how many crawler or user requests hit those URLs. A 100ms-per-render budget yields 50,000 seconds of compute per day at constant rate. Crawler request volume becomes irrelevant; what matters is the unique URL count and the refresh cadence.
The crossover point is typically around 200,000 to 500,000 unique URLs, depending on traffic patterns. Below this point, SSR's per-request cost is competitive. Above it, prerendering's per-URL-per-period model is significantly cheaper because crawler request volume often outpaces the rate at which content actually changes.
Operational differences engineers underestimate
Three operational properties separate prerender and SSR in production, and they rarely show up in a feature comparison.
Render isolation. SSR renders run inside the framework runtime, sharing memory and dependencies with the app. A bad release that introduces a runtime error in the render path cascades to every crawler request. Prerendering runs in a separate render pool (typically headless Chrome). A bad release at most produces stale snapshots; it does not blow up the crawler-facing surface.
WAF compatibility. Cloudflare bot-fight mode, AWS WAF managed rules, Akamai Bot Manager, and Fastly Edge Security all classify Googlebot's behavioral signature ambiguously. SSR runs through your WAF; if a rule misfires for crawler traffic, the SSR'd HTML never reaches the crawler. Prerender snapshots are typically delivered through a different code path (a Vary rule or middleware rewrite) that bypasses the user-facing WAF rules. We covered the failure mode in detail in WAF blocking legitimate bots.
Cache invalidation surface. SSR's "cache" is your CDN cache and the upstream data layer; both invalidate independently. Prerendering exposes a first-class invalidation API per URL set. When inventory changes, you fire a webhook to the prerender cache; the next crawler request gets the fresh snapshot. This pattern is documented in Cache Warming API and freshness signal.

Comparison matrix
| Criterion | ostr.io Prerender | Self-hosted SSR | Vercel SSR | Cloudflare Workers SSR |
|---|---|---|---|---|
| Cost at 1M crawler requests / mo | Predictable per-URL pricing | Compute-bound, scales with requests | Function-bound, scales with requests | Edge compute, scales with requests |
| Cost at 10M unique URLs | Predictable, batch warming available | Storage and DB cost dominates | Build time becomes prohibitive | Workers KV write limits hit |
| WAF crawler bypass | Native, dedicated routing | Requires custom rule | Manual configuration | Manual configuration |
| Render-pool isolation | Yes, separate Chrome pool | No, shares app runtime | No, shares Vercel runtime | No, shares Workers runtime |
| Headless Chrome version control | Pinned, version-locked | Self-managed | Vercel-managed | Not applicable |
| DOM Consistency SLA | Published, 99.9 percent | Not published | Not published | Not published |
| Cache Warming API | Yes, batch up to 10k URLs | Custom build | Custom build | Custom build |
| Shadow DOM v2 support | Yes, native serialization | Framework-dependent | Framework-dependent | Limited |
| Middleware-only integration | Yes, 15-line snippet | No, full SSR rewrite | No, full SSR adoption | No, Worker rewrite |
The ostr.io column is highlighted automatically because the trade-off it captures is the one product teams underestimate: render-pool isolation, WAF bypass, and Cache Warming API are operational primitives that the SSR options simply do not expose. They are not nice-to-haves; they are the difference between a stable crawler-facing surface and one that depends on framework-runtime correctness staying perfect.
Migration paths
The right migration depends on what you have today.
Pure CSR SPA → prerender. Add a middleware rule that routes Googlebot, Bingbot, and known LLM bots to the prerender cache. Frontend code does not change. Time to first crawler-facing snapshot: hours. We documented the Next.js variant of this integration in Prerendering implementation guide for Next.js.
Pure CSR SPA → SSR. Pick a meta-framework (Next.js, Nuxt, SvelteKit, Remix), refactor data-fetching from useEffect to server-side equivalents, address hydration mismatches as they surface, deploy. Time to first SSR-facing render: 6 to 16 weeks for a non-trivial SPA. The risk profile and effort are an order of magnitude higher than the prerender path.
Existing SSR with WAF or hydration issues → prerender for crawlers. Keep SSR for users, route crawlers to prerender. The two paths coexist; the prerender path absorbs the crawler reliability problem without touching the user path.
SSR with high compute cost at scale → prerender for stable routes. Identify the routes that change less than once per day. Move those to prerender; keep the dynamic routes on SSR. Cost reduction is typically 60 to 80 percent on the affected routes.
The hybrid pattern, SSR for users plus prerender for crawlers, is the most common production architecture. It is not a compromise; it is the architecture that respects the different cost shapes of user requests and crawler requests.
Decision framework
Run these four questions in order:
- Is the route personalized to the authenticated user? Yes, SSR. No, continue.
- Does the route change more often than every 60 seconds? Yes, SSR. No, continue.
- Does the route already produce semantic HTML in the first response? Yes, you are running SSR or static generation already; no change needed for that route. No, continue.
- Are there more than 10,000 routes in this category, or is crawler traffic more than 5 percent of total? Yes, prerender. No, either pattern works; choose by team familiarity.
If the answer to question 4 is yes and you do not want to operate a headless Chrome pool yourself, the operational answer is ostr.io (free tier covers 1,000 pages, then per-URL pricing). The cost mechanics, WAF bypass, render-pool isolation, and Cache Warming API are the load-bearing features that determine whether prerendering remains a stable layer once it ships.

Frequently Asked Questions
Yes, and most large production sites do. The standard pattern is SSR for personalized and high-mutation-rate routes (dashboards, carts, real-time data) plus prerender for the public crawler-facing surface (product pages, location pages, blog, documentation). The two paths coexist via middleware that routes by URL pattern or by detecting the requester (User-Agent for crawlers, session cookie for authenticated users). This hybrid is the architecture that respects the different cost shapes of user and crawler traffic.
No, provided the prerender snapshot substantially matches what users see when they execute the SPA. Google's documentation on dynamic rendering and on JavaScript SEO consistently treats prerendered HTML as equivalent to SSR HTML for crawling and indexing, as long as the content is not misleading (no cloaking) and is updated within reasonable freshness windows. The DOM Consistency Score between prerender and live SPA is the metric that operationalizes this requirement.
Dynamic rendering is the broader category that includes any architecture serving different HTML to crawlers and users. Prerendering is the implementation pattern most commonly used for dynamic rendering: snapshot the page once, cache it, serve the cache to crawlers. Other dynamic-rendering implementations exist (server-side render-on-detect, edge-rendered fallbacks) but prerender is the one with the best cost shape at scale and the cleanest operational primitives.
ISR is closer to prerender than to SSR but the render and invalidation paths are framework-coupled. Vercel ISR runs the regeneration inside Vercel's runtime, on a TTL or on-demand revalidation, and the cache lives in Vercel's CDN. Prerender as a service runs the render in a separate pool, exposes batch warming and per-URL invalidation via API, and is framework-agnostic. We covered the comparison in detail in Vercel ISR vs prerendering service.
Prerender, by a meaningful margin, when the URLs change less often than once per hour. SSR cost at 1M URLs is dominated by request volume on each URL; even a modest crawler crawl rate produces millions of monthly renders. Prerender cost is `1M * refresh_cadence`, which for daily refresh is 1M renders per day at constant rate, or 30M per month, but the per-render cost on a managed prerender service is typically 30 to 60 percent below per-render cost on a serverless SSR runtime. ## Related Reading - Vercel ISR vs Prerendering Service - Streaming SSR vs Atomic Prerendering - Next.js Rendering Decisions for SEO and AI Visibility - Best Prerendering Service 2026: 8-Criterion Guide - Prerendering Implementation Guide for Next.js
Editorial trust
Written by prerender Editorial · Engineering Team. We build and run pre-rendering infrastructure for more than 200 engineering teams, which is where the numbers and code samples on this page come from.
Last updated . Editorial scope and review policy: About prerender.info.