Skip to main content

Every Way to Render a React Component, From 2013 to Now

· 36 min read
Pere Pages
Software Engineer
A timeline of React rendering approaches from 2013 to the present

"Rendering" is one of those words that means three different things depending on who you ask. Before we walk the timeline, it helps to separate the layers:

  1. The rendering API — the actual function you call (ReactDOM.render, createRoot, renderToString…) to turn a component tree into DOM or HTML.
  2. The rendering strategywhere and when the HTML is produced: in the browser, on a server per-request, at build time, etc.
  3. The hydration model — how a server-produced HTML page becomes interactive on the client. The post is organized around layer 2 (strategies), because that's where the chronological and simple-to-complex story is richest — but each strategy is tagged with all three: an At a glance box names its rendering API and hydration model too. There's also a short section near the end that lists the API methods on their own, since "render a component" can literally mean those. Each strategy also has a Metrics table in web-vitals shorthand (TTFB, FCP, LCP, TBT, INP, CLS) — every acronym in this post is spelled out in the Glossary at the end.

One honest caveat up front: chronology and complexity mostly line up, but not perfectly. Islands and streaming SSR landed around the same time, for example. I've ordered primarily by conceptual complexity and noted the rough dates as we go.

A quick timeline

YearMilestone
2013React open-sourced — Client-Side Rendering (SPA)
2016Next.js popularizes SSR + hydration for React
2017–2020SSG goes mainstream (Gatsby, then getStaticProps)
2020ISR (revalidate) in Next.js
2021–2022Islands architecture (Astro)
2022React 18: concurrent rendering, streaming SSR, selective hydration
2023React Server Components stable in the Next.js App Router
2024React 19: RSC + Actions officially part of React
2023+Resumability (Qwik) as the frontier alternative to hydration

The cast of characters

Across every strategy below, the same handful of players keep showing up — what changes is who does the rendering work, and when. Here's the shared system context they all operate in:

The strategies differ in how much of the rendering happens at the CDN (a content delivery network serving build-time output), the origin server (per request), or the browser itself — and how the data source is reached.


1. Client-Side Rendering (CSR) — the original SPA

The starting point. The server sends a near-empty HTML shell with a <div id="root"> and a script tag. The browser downloads the JS bundle, React boots, and builds the entire DOM on the client.

import { createRoot } from "react-dom/client";
createRoot(document.getElementById("root")).render(<App />);

This is what you get out of the box with Vite, Create React App (RIP), or any plain single-page-app (SPA) setup.

At a glance

AspectThis strategy
Originated2013 (React's original model)
Rendering APIcreateRoot(...).render() — client
Rendering strategyClient-side render (SPA)
Hydration modelNone — the browser builds the DOM from scratch
FrameworksVite, Create React App; any plain SPA setup

Metrics

MetricThis strategy
TTFBGood — static shell from the CDN
FCP / LCPWeak — blank until the JS downloads and runs
TBT / INPWeak — ships and re-executes lots of JS

Fetching data — in the browser, after the first render (a useEffect, or a data library like React Query/SWR). That's the render → fetch → re-render waterfall.

function Profile() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch("/api/user").then((r) => r.json()).then(setUser);
}, []);
if (!user) return <Spinner />;
return <h1>{user.name}</h1>;
}

Infrastructure

CDN: yes, but only as a dumb static file host. There is no origin render server — the CDN never talks to a backend; the browser calls the data API itself.

Data flow

ProsCons
Dead-simple deployment: just static files on a CDN, no server runtime.Slow first meaningful paint: the user stares at a blank page until the JS downloads, parses, and executes.
Excellent for rich, app-like, highly interactive UIs — especially behind authentication where SEO doesn't matter (dashboards, editors, internal tools).Historically poor SEO. Crawlers have improved, but it's still a gamble for content that needs to rank.
After the initial load, navigation is instant (no full page reloads).Bundle size grows with your app, and everything is shipped to every user.
Data-fetching waterfalls: the page renders, then asks for data, then re-renders.

Use it when you're building an app, not a content site, and time-to-first-paint isn't your bottleneck.


2. Server-Side Rendering (SSR) + hydration

Render on the server, hydrate on the client. For each request, the server runs your components to a string of HTML and sends a fully-formed page. The browser paints it immediately, then downloads the JS and hydrates — React walks the existing DOM and attaches event listeners instead of rebuilding it. Popularized for React by Next.js in 2016.

At a glance

AspectThis strategy
Originated~2016 (popularized by Next.js)
Rendering APIrenderToString (server) + hydrateRoot (client)
Rendering strategyServer render, per request
Hydration modelFull — hydrateRoot walks the entire tree
FrameworksNext.js, Remix (or React on your own Node server)

Metrics

MetricThis strategy
TTFBHigher — the server renders and fetches per request
FCP / LCPStrong — real HTML on the first response
TBT / INPModerate — full-page hydration on load

Fetching data — on the server, before the HTML is sent, via a per-request data hook (Next.js getServerSideProps, or a Remix loader). No client waterfall — the data is already in the HTML.

// Next.js Pages Router — runs on the server for every request
export async function getServerSideProps() {
const user = await fetch("https://api.example.com/user").then((r) => r.json());
return { props: { user } };
}

Infrastructure

CDN: only for static JS/CSS — not the HTML. Every page comes from the origin server, which is the one thing that touches the data source. The browser hits the server directly for content.

Data flow

ProsCons
Fast First Contentful Paint (FCP) — real content is visible before any JS runs.You now need a running server (and to pay for it, scale it, and keep it warm).
SEO-friendly: crawlers get complete HTML.Time to First Byte (TTFB) can be worse than CSR — the server has to do work before sending anything.
Good for content that changes per-request or per-user (the HTML is fresh every time).The "uncanny valley": the page looks ready but isn't interactive until hydration finishes. Click too early and nothing happens.
Hydration re-runs your component tree on the client, so you pay a CPU cost twice.

Use it when you need fresh, per-request content and SEO — e-commerce product pages, news, anything personalized but public.


3. Static Site Generation (SSG)

Render at build time, not request time. Instead of generating HTML per request, you generate it once when you deploy. The output is a folder of plain HTML files that sit on a CDN. Gatsby pushed this hard around 2017; Next.js formalized it with getStaticProps in 2020.

This is exactly the model behind an Astro site or a static Next.js export.

At a glance

AspectThis strategy
Originated~2017 (Gatsby)
Rendering APIBuild-time renderToString (or React 19 prerender) + hydrateRoot
Rendering strategyRender once, at build time
Hydration modelFull — hydrated in the browser on load
FrameworksGatsby, Next.js, Astro, Docusaurus

Metrics

MetricThis strategy
TTFBBest — prebuilt HTML from the edge
FCP / LCPExcellent — content in the first response
TBT / INPModerate — still full hydration

Fetching data — once, at build time (Next.js getStaticProps, a Gatsby GraphQL query, or an Astro top-level await). The data source is never touched at request time.

// Next.js Pages Router — runs once, at build time
export async function getStaticProps() {
const posts = await getPosts();
return { props: { posts } };
}

Infrastructure

CDN: yes, and it serves the HTML itself. At runtime there is no origin server at all — the server-side work happened once at build time. The data source is only ever touched during the build.

Data flow

ProsCons
The fastest possible delivery: pre-built HTML served from the edge, no server compute per request.Build time scales with the number of pages. Tens of thousands of pages = painful builds.
Cheap and secure — there's no server runtime to attack or scale.Content is frozen at build time. To update it, you rebuild and redeploy.
Great SEO and FCP.A poor fit for highly dynamic or per-user content.

Use it when content changes infrequently and is the same for everyone — blogs, docs, marketing sites, landing pages.


4. Incremental Static Regeneration (ISR)

SSG that refreshes itself. Introduced in Next.js in 2020, ISR keeps the build-time static model but lets individual pages regenerate in the background after a configurable interval (revalidate). You get static speed without rebuilding the whole site for every content change.

At a glance

AspectThis strategy
Originated2020 (Next.js 9.5)
Rendering APISame as SSG — build/regeneration renderToString + hydrateRoot
Rendering strategyBuild-time render + background revalidation
Hydration modelFull
FrameworksNext.js (and hosts that emulate it, e.g. Netlify)

Metrics

MetricThis strategy
TTFBStatic-level — cached HTML from the edge
FCP / LCPExcellent — like SSG
TBT / INPModerate — full hydration
FreshnessBounded by the revalidate window

Fetching data — the same build-time fetch as SSG, plus a revalidate window that lets the page rebuild in the background.

// Next.js Pages Router — like SSG, but re-runs at most once per window
export async function getStaticProps() {
const posts = await getPosts();
return { props: { posts }, revalidate: 60 }; // seconds
}

Infrastructure

CDN: yes, serving the HTML — but unlike SSG, the CDN and origin server talk to each other. When a cached page goes stale, the CDN triggers the server to regenerate it, and the server writes the fresh HTML back into the cache. This bidirectional CDN↔server link is what makes ISR distinct.

Data flow

ProsCons
Static-level performance with content that can stay reasonably fresh.Framework and host lock-in (it depends on the platform's caching infrastructure).
No full rebuilds — only the pages that need updating regenerate.More moving parts: you have to reason about cache states and revalidation windows.
Scales to huge sites that would be impractical to build all at once.The first visitor after a window expires may still get a stale page while the new one builds.

Use it when you have a large, mostly-static site whose content updates on a predictable cadence — large blogs, catalogs, listings.


5. Islands architecture (partial hydration)

Mostly-static HTML with small interactive "islands." Instead of hydrating the entire page, you ship a static HTML document and hydrate only the specific components that need interactivity — each one independently. Popularized by Astro (2021–2022); the term comes from Katie Sylor-Miller and Jason Miller.

This is the model behind an Astro site built with a few React components sprinkled in: the page is HTML, and only your interactive widgets carry JS.

At a glance

AspectThis strategy
Originated~2019 (term) · 2021 (Astro)
Rendering APIStatic render at build + per-island hydrateRoot
Rendering strategyStatic HTML + partial hydration
Hydration modelPartial — only interactive islands hydrate, each independently
FrameworksAstro (also Fresh, Marko)

Metrics

MetricThis strategy
TTFBGreat — static HTML from the CDN
FCP / LCPGreat — HTML-first
TBT / INPLow — only islands ship JS

Fetching data — page-level data is fetched at build (or on the server) for the static HTML; an island can also fetch on the client when it hydrates. In Astro, the frontmatter runs at build:

---
// Astro frontmatter — runs at build time
const products = await fetch("https://api.example.com/products").then((r) => r.json());
---
<ProductList client:visible products={products} />

Infrastructure

CDN: yes, serving the HTML — same runtime shape as SSG (no origin server in the request path). The difference from SSG is on the browser side, not the infrastructure: only the island bundles are hydrated, so far less JS is downloaded from the CDN.

Data flow

ProsCons
Drastically less JavaScript shipped — you only pay for the interactive bits.The interactivity model is more constrained: islands are isolated and don't easily share live state across the page.
Excellent FCP and SEO, like SSG, but with selective interactivity.A different mental model from a "normal" SPA — you think in terms of static-by-default with explicit interactive opt-ins.
Framework-agnostic at the island level (React here, Svelte there, all on one page).Less suited to apps that are interactive everywhere (an island-heavy page loses the benefit).

Use it when you have a content-first site that needs a few interactive pieces — marketing pages, docs, or blogs where most of the page is static.


6. Streaming SSR + selective hydration

Send HTML in chunks; hydrate in priority order. Shipped with React 18 in 2022 via renderToPipeableStream. With <Suspense> boundaries, the server streams the parts of the page that are ready now and flushes slower sections (waiting on data) as they resolve. On the client, React hydrates interactive regions selectively, prioritizing the parts the user is touching.

At a glance

AspectThis strategy
Originated2022 (React 18)
Rendering APIrenderToPipeableStream (Node) / renderToReadableStream (Web) + hydrateRoot
Rendering strategyServer render, streamed in chunks
Hydration modelSelective — priority-ordered, progressive
FrameworksNext.js, Remix (React 18 streaming)

Metrics

MetricThis strategy
TTFBFast — the shell flushes before data resolves
FCPFast — the shell paints immediately
LCPFills in as slow chunks stream
TBT / INPImproved by selective hydration

Fetching data — on the server, but wrapped in a <Suspense> boundary: the shell flushes immediately and each slow region streams in when its data resolves.

<Suspense fallback={<Spinner />}>
{/* SlowFeed awaits its own data — it suspends, then streams in */}
<SlowFeed />
</Suspense>

Infrastructure

CDN: only for static assets — like plain SSR, the HTML is dynamic and comes from the origin. The extra requirement here is a streaming-capable server/host; a CDN in front can pass the stream through but can't cache it.

Data flow

ProsCons
Faster TTFB — you don't wait for the slowest data source before sending anything.More complex to reason about, and your data layer needs to play nicely with Suspense.
Progressive rendering: the shell appears immediately, slow sections stream in.Requires streaming-capable hosting/infrastructure.
Selective hydration means a slow component no longer blocks the whole page from becoming interactive.Debugging streamed, partially-hydrated pages is harder than a plain SSR page.

Use it when you have SSR pages with mixed fast/slow data and you want the fast parts to show and become interactive without waiting on the slow ones.


7. React Server Components (RSC)

Components that run only on the server and ship zero JS. The big paradigm shift. Stable in the Next.js App Router from 2023, and officially folded into React with React 19 (2024). Server Components render on the server, can access your backend/database directly, and send a serialized result to the client — not a JS bundle. You interleave them with Client Components ("use client") for the interactive parts.

// A Server Component — runs on the server, ships no JS to the browser
async function Posts() {
const posts = await db.post.findMany(); // direct data access
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

At a glance

AspectThis strategy
Originated~2020 (introduced) · 2023 (stable)
Rendering APIRSC serializer + streaming SSR; hydrateRoot for Client Components only
Rendering strategyServer Components + Client Components
Hydration modelPartial — only "use client" components hydrate
FrameworksNext.js App Router (also Waku, RedwoodJS)

Metrics

MetricThis strategy
TTFBPer request, but cacheable
FCP / LCPStrong — server-rendered HTML
TBT / INPLow — Server Components ship zero JS

Fetching data — right inside the async Server Component, with no API layer in between: await your database or an upstream fetch directly (as in the component above). Caching is per-request via fetch options (no-store, next: { revalidate }), so a single component can be static, dynamic, or revalidated on its own.

Infrastructure

CDN: for the client-component JS and static assets. The RSC payload comes from the origin server, which reads the database directly (no separate API layer). The CDN doesn't talk to the server; the browser fetches from each independently.

Data flow

ProsCons
Server Components ship zero JavaScript to the client — smaller bundles by default.A genuinely new mental model — the server/client split, serialization rules, and what can cross the boundary all take time to internalize.
Direct, secure data access (no API layer needed for the server parts; secrets stay server-side).Server Components can't use state, effects, or browser-only APIs (no useState, no event handlers).
Automatic code-splitting at the server/client boundary, and data fetching moves up the tree, killing client waterfalls.Strongly framework-dependent today (Next.js App Router, and a growing few). The ecosystem is still maturing.

Use it when you're building on a framework that supports it and want minimal client JS with first-class data access — increasingly the default for new full-stack React apps.


8. Resumability (the frontier) — honorable mention

Skip hydration entirely. Pioneered by Qwik (1.0 in 2023). Instead of re-running components on the client to "wake up" the page, the framework serializes the application state and event listeners into the HTML, and resumes exactly where the server left off — lazily downloading only the code a user actually triggers.

At a glance

AspectThis strategy
Originated~2021 (Qwik) · 1.0 in 2023
Rendering APIQwik serialize/resume — no createRoot / hydrateRoot
Rendering strategyServer render + serialize state & listeners
Hydration modelNone — resumption replaces hydration
FrameworksQwik / Qwik City

Metrics

MetricThis strategy
TTFBDepends — build-time or per request
FCP / LCPFast — HTML-first
TBT / INPNear-zero — no hydration replay

Fetching data — on the server via a route loader; the result is serialized into the HTML and read on resume, so the client never refetches it.

// Qwik City — runs on the server, result serialized into the HTML
export const useProducts = routeLoader$(async () => {
return await getProducts();
});

// in the component: const products = useProducts();

Infrastructure

CDN: yes — it serves both the resumable HTML and the fine-grained code chunks. The HTML can be produced at build time or per request; either way the CDN fronts it, and the browser pulls extra chunks from the CDN only when the user triggers them.

Data flow

ProsCons
Effectively no hydration cost, regardless of app size — startup stays cheap even for large apps.A small ecosystem and a different framework (not React proper).
Extremely fine-grained, on-demand code loading.Newer, less battle-tested, fewer engineers familiar with it.

It's not React, but it's the clearest answer to "what comes after hydration?", and it's shaping the conversation around React's own future.


One domain, many render paths

The diagrams above draw "CDN" and "Origin Server" as separate boxes, which raises a fair question: if my browser just calls example.com, where does the split actually happen? Do I wire up a CDN and a server myself?

Almost never. From the browser's side there is only one address. DNS resolves example.com to a single anycast IP (one address that routes to the nearest data center) at your host's edge network — Vercel, Netlify, Cloudflare, AWS Amplify, and friends. Every response — static file, server-rendered HTML, API route — comes back from that same origin. That's deliberate: one origin means no CORS (cross-origin request restrictions), one TLS certificate, one DNS record to manage.

So there is a proxy at the edge of the domain — but it's the platform's, not something you run. For each request, that edge router looks at the path and decides where to answer from:

example.com/_next/static/chunk.js → static asset → CDN cache (no code runs)
example.com/blog/hello → prebuilt HTML → CDN cache (SSG / ISR)
example.com/dashboard → dynamic route → serverless/edge function (your server code runs)

Two layers: framework vs. host

The examples above are all hosts. It's worth separating them from frameworks, because they're independent layers that people often conflate:

  • The framework runs at build/dev time. It decides how and when each route renders (CSR, SSR, SSG, ISR, Islands, RSC) and emits the build output.
  • The host runs at request time. It takes that output and serves it under one domain — CDN cache for static, functions for dynamic.
LayerWhat it doesExamples
FrameworkDecides how/when each route renders and produces the build outputNext.js, Remix, Gatsby, Astro, Docusaurus, Qwik
Host / platformRuns the edge at request time (CDN cache + serverless/edge functions), serving it all under one domainVercel, Netlify, Cloudflare Pages, AWS Amplify, GitHub Pages

The two are largely independent — an adapter usually lets a given framework deploy to a given host. But hosts differ in capability: a static-only host like GitHub Pages can serve CSR/SSG/Islands output but can't run functions, so SSR, ISR, and RSC need a host with a compute layer (Vercel, Netlify, Cloudflare, AWS).

The frameworks (and what they render)

A field guide to the tools named throughout this post — what each one is, the strategy it leans toward, and what else it can do. Most let you opt into other strategies per route.

Framework / toolWhat it isDefault renderingAlso supports
Vite / Create React App (RIP)Build tool / classic SPA scaffoldCSR (#1)— (you add a router + data layer yourself)
Next.jsFull-stack React frameworkPer-route mix; App Router is RSC-first (#7)CSR, SSR, SSG, ISR, Streaming (#1–#6)
Remix / React RouterFull-stack, web-standards frameworkSSR (#2), streaming-friendlyStatic export; edge runtimes
GatsbyReact static-site generatorSSG (#3)SSR, DSG (deferred static generation)
AstroContent-first, multi-framework site builderSSG + Islands (#3, #5)SSR via an adapter
DocusaurusReact docs/site generator (this very blog)SSG (#3)Client-side hydration only
Qwik / Qwik CityResumable frameworkResumability (#8)SSR/SSG output, without hydration

Where the split lives in your source code

Zooming into a single framework: you rarely configure that router by hand. The CDN-vs-server decision is derived from how you wrote each route, and the framework's build step translates it into deployment artifacts. Using Next.js as the example:

What you write in the routeWhat it compiles toServed from
A plain page, no dynamic APIsPrerendered HTML at buildCDN (static)
export const revalidate = 60Prerendered + background regenCDN, refreshed by a function (ISR)
Uses cookies(), headers(), or fetch(url, { cache: "no-store" })Rendered per requestServerless / edge function
export const dynamic = "force-dynamic"Always per requestServerless / edge function
A file in /public or an imported assetHashed static fileCDN

At npm run build, the framework emits a manifest: which routes are prerendered HTML (upload to the CDN), which are functions (deploy as serverless/edge), and the routing rules between them. The host's build adapter reads that manifest and configures the edge router for you. That's the "transparent" part — the platform is turning your per-route rendering choices into infrastructure.

The decision is mostly your code; the enforcement is the platform's edge. You can override with explicit config (vercel.json, netlify.toml, next.config.js rewrites/headers, _redirects), but the default is convention over configuration. And the practical upshot ties back to every strategy above: they differ mainly in how much of your traffic the edge can answer straight from cache versus how often it has to wake a function.


No magic: who does what

All the diagrams above show what happens, which can still feel like sorcery. It isn't — every piece has exactly one job, and you (the developer) only touch a few of them. Here's the whole cast, de-magicked.

The responsibility map

PlayerOwnsDoes not do
React (the library)Turning components into HTML/DOM and defining hydration — createRoot, renderToString, hydrateRoot, the streaming APIsDecide per-route strategy, build, cache, or deploy anything
The framework (Next.js, Astro, Remix, Gatsby…)Per-route rendering decisions, routing, data-fetching conventions, and the build that emits static files + function bundlesHost your app — it produces artifacts, it doesn't run them in production
The build adapter (framework↔host glue)Translating build output into a host's format — static assets, serverless/edge functions, routing + cache rulesAny rendering logic of its own
The host / platform (Vercel, Netlify, Cloudflare, or your own infra)The edge router, CDN cache, function runtime, TLS, and one domainDecide how routes render — it just runs what the adapter handed it
You (the developer)Choosing the framework, writing components, picking each route's strategy, cache windows, secrets, and the data sourceThe plumbing above — most of it is convention + config

So, is it all the framework? No. React renders, the framework orchestrates, the adapter packages, the host runs, and you make the decisions. No single piece is magic; each has one job.

What you actually do

In practice, your work is short:

  1. Pick a framework that fits the job (static blog → Docusaurus/Astro; full-stack app → Next.js/Remix).
  2. Write components. Mark the client/server boundary where the framework needs it ("use client" in RSC).
  3. Choose each route's rendering — usually by convention: static by default; opt into dynamic with cookies()/headers()/no-store; or schedule refresh with revalidate.
  4. Set caching/revalidation and provide server-only secrets via environment variables.
  5. Point routes at your data source (database/API) from the server side.
  6. Deploy to a host (managed or your own), then check the build output to confirm which routes went static vs. dynamic.

That's it — you're mostly choosing strategies and wiring data. The framework and host handle the mechanics.

What can go wrong (and how to debug it)

SymptomLikely causeHow to debug / fix
Hydration mismatch warning; UI flickers or resets on loadServer HTML ≠ first client render — non-deterministic output (Date.now(), Math.random(), window, locale/timezone)The console warning names the node; render the same thing on both sides, or defer client-only bits to useEffect / a "mounted" flag
window/document is not defined at build or request timeBrowser API used during SSR or inside a Server ComponentGuard with typeof window !== "undefined", move it to useEffect, or mark the component "use client"
A page you expected to be static is slow / uncacheableAccidentally opted into dynamic (cookies(), headers(), no-store)Check the framework's build output (Next.js marks routes ○ Static / ƒ Dynamic); remove the dynamic API or make the read static
A secret shows up in the browser bundleServer-only env imported into a client componentKeep secrets in server code / non-NEXT_PUBLIC_ env; audit the client bundle
Stale content after an update (ISR)Still inside the revalidate window, or the cache wasn't purgedInspect cache headers (age, cache-control, x-vercel-cache) with curl -I; trigger on-demand revalidation
Nothing streams; the whole page waitsData layer isn't Suspense-integrated, or there are no <Suspense> boundariesAdd boundaries around slow parts; confirm the server uses renderToPipeableStream
Huge JS bundle, slow interactivityOver-hydration — too much shipped to the clientRun a bundle analyzer; push work into Server Components or islands

Debugging toolkit: curl -I <url> for cache/render headers · view-source vs. the live DOM (empty shell = CSR, full HTML = SSR/SSG) · disable JS to see the raw SSR output · the Network tab for TTFB, streamed chunks, and bundle sizes · your framework's build output for the static/dynamic map · Lighthouse for FCP/LCP.

Infrastructure needed

StrategyMinimum infrastructureCompute at request time?
CSR, SSG, IslandsStatic file host + CDN (GitHub Pages, S3 + CloudFront, any CDN)No — pure static delivery
ISRStatic host + CDN plus a function to regenerate pagesOccasionally, on revalidation
SSR, Streaming SSR, RSCCDN for assets + a compute runtime (serverless/edge functions or a Node server) + the router that splits themYes — every dynamic request
all of the aboveA data source (DB/API) reachable from the compute layer, plus DNS + TLS

The rule of thumb: the more dynamic the strategy, the more you need a compute layer, not just a CDN.

Rolling your own edge

You don't need Vercel — you can assemble the same static/dynamic split from primitives. The "edge router" is just a reverse proxy or CDN routing rule that dispatches by path: cacheable/static → object storage; dynamic → a function or server.

Assembling it, piece by piece:

PieceHow to build it
Static assets & prebuilt HTMLObject storage (S3 / GCS / Cloudflare R2) behind a CDN (CloudFront / Cloudflare / Fastly), with long cache headers and hashed filenames
Dynamic routesServerless functions (AWS Lambda, Cloudflare Workers, Google Cloud Run) or a long-running Node server in a container (Fly.io, Railway, ECS, Kubernetes)
The router / one domainA reverse proxy (Nginx) or your CDN's path rules: /_static/* and cacheable paths → the bucket, everything else → the compute origin. Terminate TLS here and point DNS at it
ISR-style revalidationstale-while-revalidate cache headers plus a function that rebuilds a page and writes the fresh HTML back to the bucket/cache
Self-hosting a frameworkMost ship a Node/container adapter: Next.js output: "standalone" (or OpenNext for AWS primitives), Astro/Remix Node adapters — run that behind the same CDN

This is exactly what the managed platforms automate for you. Doing it yourself is more work and more knobs, but there's still no magic — it's a proxy, a bucket, a function runtime, and a cache, all wired to one domain.


The API layer (the literal "render" methods)

If by "rendering a component" you meant the actual function calls, here's how those evolved:

MethodRole & status
ReactDOM.render(...)The original client entry point. Removed in React 19.
ReactDOM.hydrate(...)The SSR counterpart for attaching to server HTML. Also legacy.
createRoot(...) / hydrateRoot(...)The React 18 replacements that unlock concurrent features.
renderToString(...) / renderToStaticMarkup(...)Synchronous server rendering to an HTML string (the latter omits React's bookkeeping attributes, for emails or fully-static output).
renderToPipeableStream(...) (Node) / renderToReadableStream(...) (Web/Edge)The React 18 streaming APIs.
prerender(...) / prerenderToNodeStream(...)React 19 static APIs that wait for all data before resolving, for prerendering RSC output.

How to choose (a short decision guide)

If your situation is…Reach for
Static content, same for everyoneSSG, or Islands if you need a few interactive pieces
Static-ish but updates regularlyISR
Per-request or personalized content that needs SEOSSR, and streaming SSR if data sources are uneven
App behind auth, SEO irrelevantCSR is still perfectly fine, and the simplest thing that works
New full-stack app, want minimal client JSRSC via a supporting framework

A useful rule of thumb: start with the simplest strategy that meets your constraints, and only reach for more complexity when a real requirement (SEO, FCP, bundle size, data freshness) forces your hand. Each step up this list buys you something, but it costs you infrastructure, mental overhead, or both.


The throughline

The whole arc has been a steady push of work away from the user's device and toward build time or the server — while trying to ship less and less JavaScript. CSR puts everything on the client. SSR and SSG move the initial render off it. Islands and RSC attack the JS bundle directly. Resumability questions whether hydration needs to exist at all.

You don't need to pick one for your whole career, or even your whole app. Modern frameworks let you mix these per-route and per-component — a static marketing page, a streamed dashboard, and an island-powered widget can all live in the same project. The skill isn't memorizing the list; it's matching each page's actual constraints to the cheapest strategy that satisfies them.


Glossary

Every acronym and term used above, in one place.

Rendering strategies

TermMeaning
CSR — Client-Side RenderingThe browser downloads a near-empty shell + JS and builds the DOM itself. (#1)
SSR — Server-Side RenderingThe server renders HTML per request; the client then hydrates it. (#2)
SSG — Static Site GenerationHTML rendered once at build time and served from a CDN. (#3)
ISR — Incremental Static RegenerationSSG plus background regeneration after a revalidate window. (#4)
Islands — partial hydrationStatic HTML with small, independently hydrated interactive regions. (#5)
Streaming SSRServer HTML flushed in chunks via <Suspense>, hydrated selectively. (#6)
RSC — React Server ComponentsComponents that render only on the server and ship zero JS. (#7)
ResumabilitySerialize server state into the HTML and resume on the client without hydration. (#8)
DSG — Deferred Static GenerationGatsby's on-demand build: a page is generated on first request, then cached.

Concepts & infrastructure

TermMeaning
HydrationAttaching React's event listeners and state to server-rendered HTML to make it interactive.
SPA — Single-Page ApplicationAn app that renders and routes entirely in the browser after the first load.
CDN — Content Delivery NetworkA geographically distributed cache that serves static files (and cached HTML) from the edge.
EdgeCompute and cache running close to users, at the CDN's points of presence.
Serverless / edge functionServer code the host runs on demand per request, with no always-on server.
AdapterFramework↔host glue that packages build output into a specific host's format.
ManifestThe build's map of which routes are static HTML vs. functions, plus the routing rules.
SuspenseA React boundary that shows a fallback while its children await data or code.
RSC payloadThe serialized Server-Component output streamed to the client (not a JS bundle).
AnycastOne IP address that routes each user to the nearest data center.
CORS — Cross-Origin Resource SharingBrowser rules governing requests to a different origin.
TLSThe encryption behind HTTPS; usually terminated at the edge/proxy.

Performance metrics

LCP, INP, and CLS are Google's Core Web Vitals; TTFB and FCP round out what a Lighthouse run reports.

MetricWhat it measuresImproved by
TTFB — Time to First ByteHow fast the server/CDN starts respondingCDN/edge caching, SSG/ISR, fast origins
FCP — First Contentful PaintWhen the first content appearsServer/build-time HTML (SSR/SSG), small critical CSS
LCP — Largest Contentful PaintWhen the main content is visibleSSR/SSG, image priority + optimization
TTI — Time to InteractiveWhen the page reliably responds to inputLess/deferred JS, partial or selective hydration
TBT — Total Blocking TimeMain-thread blocking during loadSmaller bundles, Islands/RSC, code-splitting
INP — Interaction to Next PaintResponsiveness to input (replaced FID in 2024)Less client JS, lighter event handlers
CLS — Cumulative Layout ShiftVisual stability (unexpected shifts)Reserved space for images/ads, stable fonts
FID — First Input DelayLegacy responsiveness metric, replaced by INP in 2024(superseded)
Bundle / hydration sizeJS shipped and re-executed on the clientRSC, Islands, resumability, tree-shaking

The trend across the whole post: less client JS → better TBT/INP, and HTML produced earlier (build or server) → better TTFB/FCP/LCP.