When a React, Angular or Vue app starts having SEO problems, someone inevitably suggests "just use SSR." Sometimes that's the right call. Sometimes it's months of refactoring for a problem you could solve in a day. Each strategy makes a different trade-off between build complexity, runtime cost, and content freshness.
CSR — Client-Side Rendering
In a standard React, Angular or Vue app, rendering happens entirely in the browser:
- Server returns a minimal HTML shell.
- Browser downloads the JS bundle.
- Framework executes, fetches data, paints the UI.
Good at: developer simplicity, rich interactivity, no server infrastructure beyond a static file host.
Breaks down when: crawlers and bots that skip JavaScript receive the empty shell. This affects search indexing, OG previews, and any tool that parses your HTML. For exactly what crawlers see, read How a Search Crawler Actually Sees Your React App.
SSR — Server-Side Rendering
Every HTTP request triggers a server-side render: the server runs your framework code, fetches data, produces a complete HTML document, and sends it to the client. React then hydrates the page — attaches event listeners to the already-visible DOM.
Good at:
- Crawlers, bots, and users all get the same fully rendered HTML.
- Content is always fresh — no build step needed to reflect data changes.
- Faster perceived FCP on content-heavy pages since HTML is ready before the JS bundle executes.
Gets complicated because:
- You need a persistent Node.js (or equivalent) server. Deployment is no longer "upload a folder to S3."
- Server costs scale with traffic — every request is CPU work.
- Your code must be isomorphic. Libraries that use
window,document, or browser APIs need guards; third-party components may not support SSR at all. - CDN caching is possible but cache invalidation becomes your problem.
Fits when: content changes per-request and per-user — authenticated dashboards, personalized feeds, real-time data.
SSG — Static Site Generation
HTML is generated once at build time. The framework fetches all data, outputs a directory of .html files, and you deploy those to a CDN.
Good at:
- Fastest possible response time — serving a pre-built file from a CDN edge is as fast as HTTP gets.
- Zero per-request compute cost.
- Simple, resilient infrastructure.
Gets complicated because:
- Build times grow with content volume — thousands of pages can mean minutes per deploy.
- Content is stale until the next rebuild; a CMS update doesn't appear until you trigger and ship a new build.
- Highly dynamic content requires client-side fetching on top of the static shell, reintroducing the CSR problem.
Fits when: content is mostly static, updates on a predictable schedule, and no per-user server logic is needed.
Prerendering
Prerendering works differently from SSR and SSG. It doesn't change what your app serves to real users — they still get the SPA. It intercepts bot requests and serves a pre-generated HTML snapshot instead.
The snapshot is generated by running your page in a headless Chromium instance, so it reflects the fully-executed JavaScript output.
Good at:
- Fixes crawler and bot visibility without touching your app architecture.
- No isomorphic code requirement.
- Works across React, Vue, Angular, and non-framework JS setups.
- Meta tag overrides (
title,description,keywords) per URL or via regex — no code deploy. - Cache warming via sitemap eliminates the cold-start on first request.
Limits:
- Doesn't improve runtime performance for real users — they still load and execute the JS bundle.
- Snapshot freshness depends on cache TTL; very frequently updated pages need correct invalidation.
Fits when: you have an existing SPA with a crawlability or social preview problem and rewriting to SSR or SSG is a disproportionate investment.
Side-by-side
| CSR | SSR | SSG | Prerendering | |
|---|---|---|---|---|
| Crawler-readable HTML | ✗ | ✓ | ✓ | ✓ (bots only) |
| Always fresh content | ✓ | ✓ | ✗ (rebuild needed) | ✓ (within TTL) |
| Server runtime required | ✗ | ✓ | ✗ | external service |
| App code changes needed | ✗ | ✓ (isomorphic) | ✓ (build integration) | ✗ |
| Scales to large content sets | ✓ | ✓ | slow builds at scale | ✓ |
| User-personalized responses | ✓ | ✓ | ✗ | ✗ |
How to pick
Match the strategy to your constraint:
- Crawlers don't see my content → Prerendering, if you already have a working SPA.
- Building a new app, SEO from day one → SSR or SSG depending on content dynamism.
- Content rarely changes, same for all users → SSG.
- Content is personalized or real-time → SSR.
- Change meta tags on hundreds of pages without a deploy → Prerendering + meta overrider.
These strategies can coexist. A Next.js app can still use prerendering for URL patterns where the server render is too slow or where you need bulk meta overrides.