Server-Side Rendering vs Client-Side Rendering
The render seam shapes the API contract. SSR's full-payload vs CSR's many-small-calls, and the hybrid in between.
Summary#
Server-Side Rendering (SSR) is the pattern where the server returns rendered HTML — the page’s content is in the first response. Client-Side Rendering (CSR) is the pattern where the server returns a near-empty HTML shell + a JavaScript bundle; the bundle fetches data and renders the page in the browser.
The two choices land in different places on three axes:
- Time to first paint — SSR wins on first paint (HTML arrives ready to render). CSR’s first paint is a blank shell; the user waits for JS to download, parse, execute, and fetch data.
- Time to interactivity — Roughly tied. SSR pages need hydration JS before they’re interactive; CSR pages are interactive as soon as the JS finishes.
- Subsequent navigation — CSR wins. The shell is already loaded; navigation is a JSON fetch + a client-side route change. SSR pages re-render server-side on every navigation unless paired with client-side routing.
Hybrid rendering (SSR for the first page, CSR for subsequent navigation) is the modern default. Next.js, Remix, SvelteKit, Astro, and most React-on-server frameworks ship this shape.
The API designer’s view: SSR pages want one fat call that returns everything the page renders; CSR pages want many small, cacheable calls that each fetch a fragment. The same data, three orders of magnitude differences in chattiness. The choice between SSR and CSR shapes the API contract before the API designer writes their first line.
Why it matters#
Three reasons the rendering choice belongs in the API-design conversation:
- It dictates the chattiness budget. A CSR page that makes 12 API calls on first load is fine on a fibre connection and a disaster on cellular. An SSR page that makes those 12 calls server-side, in parallel, and returns one rendered HTML is much cheaper on the wire. The API has to support both — typically a “page bundle” endpoint for SSR plus the fragment endpoints for CSR.
- It decides where authentication happens. SSR pulls the session cookie out of the inbound request and renders personalised HTML. CSR uses a bearer token in JavaScript and fetches personal data via API. The two paths have different security shapes.
- It shapes SEO and social previews. Search engines and OpenGraph crawlers read HTML; they don’t run your JavaScript reliably. A CSR-only app is invisible to half the web’s discovery surface. SSR fixes this; static generation (SSG) fixes it harder.
The senior signal in an interview: “Before I design the data API, I want to know whether the front-end renders server-side or client-side — the contract changes shape either way.”
How it works#
SSR — server renders HTML#
The flow on a first request:
Browser Server Data services │ GET /products/42 │ │ │──────────────────────────────►│ │ │ │ fetch product 42 │ │ │───────────────────────────────►│ │ │ fetch related products │ │ │───────────────────────────────►│ │ │ fetch reviews │ │ │───────────────────────────────►│ │ │ render React/Vue/Svelte to HTML │ 200 OK │ │ │ <html>...full page...</html> │ │ │◄──────────────────────────────│ │ │ parse HTML, paint │ │ │ fetch + execute hydration JS │ │ │ page now interactive │ │The server makes three internal calls and returns rendered HTML. Time to first paint is governed by server processing time + network time — typically 200-500ms on a warm origin.
API contract for SSR: one “page bundle” endpoint per route that returns everything the page renders. The endpoint is internal (not exposed to clients) and fans out to the fragment services in parallel.
GET /internal/page-bundle/products/42 HTTP/1.1→ { "product": { ... }, "related": [ ... ], "reviews": { items: [...], next: "abc" } }CSR — browser renders HTML#
Browser Server Data services │ GET /products/42 │ │ │──────────────────────────────►│ │ │ 200 OK │ │ │ <html>shell + script src</html> │ │◄──────────────────────────────│ │ │ parse, paint blank shell │ │ │ GET /static/app.js │ │ │──────────────────────────────►│ │ │ 200 OK (app.js) │ │ │◄──────────────────────────────│ │ │ parse, execute JS, mount │ │ │ GET /api/products/42 │ │ │──────────────────────────────►│ fetch product │ │ │───────────────────────────────►│ │ 200 OK (product JSON) │ │ │◄──────────────────────────────│ │ │ GET /api/products/42/related │ │ │──────────────────────────────►│───────────────────────────────►│ │ 200 OK (related JSON) │ │ │◄──────────────────────────────│ │ │ GET /api/products/42/reviews │ │ │──────────────────────────────►│───────────────────────────────►│ │ 200 OK (reviews JSON) │ │ │◄──────────────────────────────│ │ │ render product page │ │API contract for CSR: many small, cacheable fragment endpoints. Each is a clean REST resource. The browser composes the page.
The first-paint cost is real — between the shell paint and the data paint there’s a perceptible blank period. Mitigations include skeleton screens, progressive rendering, and resource hints (<link rel="preload">).
Hybrid — SSR first, CSR after#
The modern default. The first navigation gets SSR; subsequent navigations are CSR-style JSON fetches.
First nav (SSR) Subsequent nav (CSR) │ │ Browser → server → HTML Browser → server → JSON server fans out to data client fetches one or two one round-trip to TTFB routing happens in JS router fetches next page's dataThis pattern needs two API contracts:
- A page-bundle endpoint for SSR (
GET /api/page-bundle/products/42). - Fragment endpoints for CSR navigation (
GET /api/products/42,GET /api/products/42/related, etc.).
Mature frameworks (Next.js, Remix) automate the page-bundle composition — you write a route’s data loader once, the framework calls it server-side on SSR and client-side on CSR. The API design surface is the fragment endpoints; the page bundle is implicit.
Static Site Generation — SSR at build time#
SSG generates the HTML at build time (not request time). The wire shape is identical to SSR — HTML in the first response — but the server isn’t computing anything. A CDN serves the prebuilt page.
API contract for SSG: the data is captured into the HTML at build. There’s no runtime API call for the static parts. Dynamic islands (a “live” comment count, a per-user header) fall back to CSR-style fragment fetches.
Astro (this site’s framework), Hugo, Jekyll, Gatsby, Eleventy ship SSG. The pattern is dominant for content sites where the data doesn’t change per user.
Edge SSR — SSR at the CDN#
Render at the CDN edge, not the origin. Cloudflare Workers, Vercel Edge Functions, Netlify Edge: a JavaScript function runs at a PoP close to the user, makes upstream calls to the origin API, returns HTML.
The latency win is real — the user-to-edge hop is < 50ms worldwide, not the 200-500ms transcontinental hop to origin. The cost: every upstream call still goes to origin; the edge isn’t a cache (without explicit caching).
API contract for edge SSR: the same fragment endpoints, but called from the edge. Latency from edge to origin matters more than usual.
Variants and trade-offs#
SSR — one fat call. Server renders HTML. Time-to-first-paint is short. SEO works. Personalisation works on first paint (server reads session). API contract is one “page bundle” endpoint per route. Cost: server work per request, slower subsequent navigation unless paired with client-side routing, larger initial HTML.
CSR — many small calls. Server returns shell + JS; browser fetches data. Time-to-first-paint includes JS download + parse. Subsequent navigation is fast. API contract is many small REST endpoints. Cost: blank-shell flash on first paint, SEO needs help, every fetch is a round-trip on cellular.
| Dimension | SSR | CSR | Hybrid (SSR + CSR) | SSG |
|---|---|---|---|---|
| Time to first paint | ~200-500ms | ~600-1500ms | ~200-500ms first; ~50ms after | ~50ms (CDN) |
| Time to interactive | ~500-800ms | ~600-1500ms | ~500-800ms first; ~100ms after | ~100ms |
| SEO | Good | Needs prerender service | Good | Excellent |
| Personalisation on first paint | Yes (session cookie) | No (need extra round-trip) | Yes (first paint) | No (dynamic islands) |
| Subsequent navigation | Slow (full server render) | Fast (JS routing) | Fast | Fast (prefetched HTML) |
| API contract | One page-bundle endpoint | Many fragment endpoints | Both | Static-time fetches + dynamic-island fragments |
| Server cost per request | Highest | Lowest | Mixed | Zero (build time only) |
| Right for | Personalised content sites, SaaS dashboards | App-shaped UIs after auth | Most modern web products | Blogs, marketing, docs |
The pragmatic progression:
- A blog or docs site: SSG. No reason to render per-request.
- A SaaS dashboard behind login: CSR (or hybrid with auth-gated SSR). Search engines don’t index logged-in pages.
- A consumer e-commerce site: Hybrid (SSR + CSR). SEO matters; first paint matters; subsequent nav speed matters. Next.js and Remix exist for this case.
- An admin tool with no SEO need: CSR. Simplest API contract; one shell, many fragments.
When this is asked in interviews#
The render seam comes up in three places in API-design interviews:
- In any consumer-facing product design — “where does the rendering happen?” Senior answer: hybrid. SSR for first paint (SEO + speed), CSR for subsequent navigation. The data API exposes fragments; the SSR layer composes them.
- In any mobile-app + web design — the mobile app is CSR-shaped (native UI, fragment API). The web is hybrid. The API serves both; the fragments are the lingua franca.
- In any “we have a SPA, our SEO is bad” question — senior answer is SSR for crawlable pages, dynamic-island CSR for the personalised bits.
Specific points to make:
- Name the contract difference. SSR wants one fat page-bundle endpoint; CSR wants many small fragment endpoints; hybrid wants both. The data services underneath are the same.
- Tie it to LCP. SSR shortens Largest Contentful Paint; CSR lengthens it. Core Web Vitals reward SSR.
- Tie it to personalisation. SSR reads cookies server-side and renders personalised HTML. CSR makes an extra round-trip after JS loads. The first-paint cost of personalised CSR is the user staring at a logged-out shell for a beat.
- Tie it to caching. SSR HTML can sit on a CDN with
Vary: Cookie(per-user) or unconditionally (anonymous). CSR fragment endpoints cache per URL — finer-grained, easier to invalidate per fragment. - Don’t over-claim. “Hybrid is always best” is the senior answer; “SSR is always best” or “CSR is always best” is the junior answer. The right call depends on the surface.
The strongest one-liner: “SSR wants one fat page-bundle call; CSR wants many small fragment calls. Hybrid is the modern default — SSR the first paint, CSR the navigation after. The API serves the fragments; the SSR layer composes them.”
Related concepts#
- Speeding Up Web Page Loading — the LCP / FID / CLS picture; SSR vs CSR is one of the largest levers on it.
- Client-Adapting APIs — BFFs / GraphQL as the layer that does page-bundle composition for SSR.
- Data Fetching Patterns — the four levers; SSR pulls eager, CSR pulls lazy by default.
- Resource Hints and Debouncing — preload / prefetch help CSR’s blank-shell flash.
- Caching at Different Layers — SSR HTML caches differently than CSR fragments; both need a story.