Frontend / Guide
Where the HTML gets made decides everything downstream
Rendering model is the first of the six framework questions because it sets the others: how much JavaScript ships, what hosting costs, whether search engines see content on the first pass, and how fast a mid-range phone paints. Nine models are in use in 2026; most pages need one of three.
About 8 min read. Versions verified September 2026.
The verdict
Content gets built once: static HTML from Astro 7, with islands for the few components that need JavaScript. Applications get server-rendered with streaming - Next.js 16 Server Components or SvelteKit 2 with a CDN cache in front. Pure client-side rendering is correct in exactly one place: behind a login, where nothing needs indexing. Pick by the page, not by the framework.
Nine rendering models, one table
On a slow connection the only column that matters is the second one. Every developer-experience promise is paid for in what the browser has to download and execute.
| Model | What ships to the browser | Best for | Who does it well |
|---|---|---|---|
| Client-side rendering (CSR) | An empty shell and a JavaScript bundle. The browser downloads the app, fetches data, then builds the DOM. | Apps behind a login, editors, internal tools - anything useless without JavaScript. | Vite 8 with React, Vue, Svelte or Solid; TanStack Router. |
| Server-side rendering (SSR) | Complete HTML per request, then the JavaScript that hydrates it into a live app. | Pages that must be indexed and personalized, or show data fresher than a deploy. | Next.js 16, Nuxt 4, SvelteKit 2, SolidStart, Angular SSR, Astro 7 in server mode. |
| Streaming SSR | HTML in chunks: the shell immediately, slow sections later as their data resolves. | Apps where one slow query should not hold the page. | React 19 Suspense in Next.js 16, SvelteKit streamed promises, Nuxt 4, SolidStart. |
| Static site generation (SSG) | HTML files built once at deploy and served from a CDN. No server runs at request time. | Content that changes when you deploy: marketing, docs, blogs, changelogs. | Astro 7, SvelteKit prerender, Next.js static export, Eleventy, Hugo. |
| Incremental static regeneration (ISR) | Static HTML a server regenerates after a time window or when a webhook or tag invalidates it. | Catalogs and CMS content with thousands of pages that change a few at a time. | Next.js 16 revalidation and Cache Components; Nuxt 4 route rules on Nitro. |
| Islands | Static HTML plus a small bundle per interactive component, each hydrated on its own schedule. | Content sites with a search box or a comments widget - interactivity in corners. | Astro 7 (client islands and server islands), Fresh. |
| React Server Components (RSC) | HTML plus a serialized component tree. Server components never ship code; only client-boundary subtrees hydrate. | Applications mixing data-heavy read views with interactive ones. | Next.js 16 App Router; the primitives are React 19. |
| Edge rendering | The same output as SSR, produced at a CDN point of presence near the user instead of one region. | Geo-personalization, A/B routing, auth checks and redirects that should not hit an origin. | Cloudflare Workers via the Astro, SvelteKit and Nuxt adapters; Vercel Edge for Next.js middleware. |
| Resumability | HTML with application state serialized in. Nothing re-executes on load; handlers download on first interaction. | Content-heavy pages with broad interactivity, where a hydration pass would cost too much. | Qwik 1.20. Nobody else ships it in production form. |
Gotcha: most frameworks now do several of these per route. The question is not which model the framework uses but which model this page uses.
Pick by what the page is
Two questions per page: does it need to be indexed, and how often does the content change relative to how often you deploy?
| Page type | Model | Why |
|---|---|---|
| Marketing page | SSG + islands | Indexed, changes on deploy, and the only JavaScript it needs is the signup form. Every kilobyte is a conversion number. |
| Docs | SSG | Hundreds of Markdown pages, all indexed. A search island is the only interactive piece. |
| Blog | SSG, or ISR | Static until editors publish more often than you deploy or the archive is too large to rebuild; then ISR regenerates only what changed. |
| Product listing | ISR, or cached SSR | Indexed and price-sensitive. Regenerate on a short window or an inventory webhook; the stock badge can be a small client-side fetch. |
| Dashboard behind auth | Streaming SSR or CSR | Not indexed, so CSR is legitimate. Streaming SSR with Server Components wins when first paint matters. |
| Checkout | SSR, uncached | Per-user data, never static. Stream the slow parts - shipping quotes, tax - so the form is usable first. |
| Editor or canvas app | CSR | The page is the JavaScript. Server-rendering an editor produces HTML nobody can use until the app takes over. |
| Comment-heavy page | SSG or ISR + island | The article is static and indexed; the comments are a server island or Server Component that fetches fresh on each view. |
Gotcha: one site usually has four of these rows in it. Astro 7 and Next.js 16 both cover the whole column; they differ on which end they start from.
Hydration is the bill
Server rendering gives the browser finished HTML, which is why it paints fast. Then the framework has to make that HTML interactive: download the component code, execute it, rebuild the component tree in memory, and attach handlers to the DOM the server already produced. That step is hydration, and it is where classic SSR quietly spends what it saved. Until it finishes, the page looks ready and is not.
On a developer laptop it is imperceptible. On a mid-range Android phone over cellular it is the dominant cost of the page: the JavaScript outweighs the HTML it hydrates, executing it is single-threaded, and the CPU is a fraction of the laptop's. The two newest models exist to close that gap.
Islands attack the amount. Astro 7 ships each interactive component as its own bundle and hydrates it only when a directive says to, so a page with three widgets pays for three widgets:
---
import SearchBox from '../components/SearchBox.jsx';
import Comments from '../components/Comments.svelte';
import PricingTable from '../components/PricingTable.astro';
---
<SearchBox client:idle /> <!-- hydrate once the main thread is free -->
<Comments client:visible /> <!-- hydrate when scrolled into view -->
<PricingTable /> <!-- no directive: static HTML, zero JS --> React Server Components attack the tree. A server component never sends its code or dependencies to the browser - a Markdown renderer or a database client can live there for free. Only subtrees marked 'use client' hydrate, so the boundary belongs on the like button, not the page around it.
Gotcha: both models only help if the boundaries stay small. A 'use client' at the top of a layout, or client:load on a wrapper, hydrates everything beneath it and you are back to the full bill with extra ceremony.
Caching decides whether SSR is fast
A static page is fast because a CDN edge hands over a file it already has. An uncached server-rendered page is slower on every request, because a server has to wake up, run your code, wait on your database and build the HTML before the first byte leaves. SSR does not beat static; SSR plus a cache can match it, and the cache is what teams forget to design.
There are three places to put it. The CDN cache is cheapest and most general: set a Cache-Control header and any host serves the rendered HTML from the edge until it expires. ISR is the same idea managed by the framework, with per-page invalidation by tag. Next.js 16 made that explicit - Cache Components and the "use cache" directive mark exactly which functions and components are cached, with cacheLife() for the window and cacheTag() for on-demand invalidation. Nuxt 4 does the equivalent with route rules on Nitro.
The header that makes a server-rendered page behave like a static one is stale-while-revalidate: serve the cached copy instantly, refresh in the background, and only make a user wait when the cache is cold.
Cache-Control: public, max-age=0, s-maxage=600, stale-while-revalidate=86400
# max-age=0 browsers always revalidate, so a deploy is visible at once
# s-maxage=600 the CDN serves the same HTML for ten minutes without asking
# stale-while-revalidate after that, serve the stale copy and refetch in the background Anything personalized - a username in the header, a cart count - breaks page-level caching because the HTML differs per user. Move that part into a client-side fetch, a server island or a streamed boundary, and cache the rest as if nobody were logged in. That one refactor usually decides whether the app needs a bigger server.
Gotcha: Cache-Control: no-store also removes a page from the browser's back-forward cache, so the back button re-renders instead of restoring. Reserve it for pages that must not be stored, such as checkout and account settings.
Gotchas
These produce a support ticket rather than a slow page. Each is a rendering model doing exactly what it was told.
| Symptom | Cause and fix |
|---|---|
| "Google renders JavaScript, so CSR is fine for SEO" | Half true. Googlebot executes JavaScript in a second pass that can lag the HTML crawl by hours or days, with a render budget large sites exhaust; most other crawlers do not render at all. Anything that must rank ships as HTML. |
| Edge-rendered pages are slower than the old origin | The code moved to the edge and the database did not, so every query crosses an ocean and a cold isolate adds startup on top. Keep database-bound rendering in the database's region, use the edge for routing, auth checks and redirects, or move the data to a replicated store. |
| The same request fires on the server and again in the browser | Double fetching: the server loaded the data to render, then a client effect loaded it again on mount. Pass the server result down as props or through the framework's loader and dehydrate it into the client cache; TanStack Query, SvelteKit and Nuxt all document the path. |
| Build fails with a server-only module in a client bundle | A client component imported a database client, a secret or a Node API, often through a shared utilities file. Split server code into modules only server components import, guard them with the server-only package, and pass data across the boundary as serializable props. |
| Deploy went out; users still see last week's page | ISR and CDN caches survive a deploy unless something invalidates them, and a long s-maxage without a purge does exactly this. Purge the CDN after the deployment is live, not before, and tag cached content so a data change invalidates one page, not all of them. |
| Console shows a hydration mismatch and the UI flickers | The server and the browser rendered different markup - a local-time timestamp, a random ID, or a browser extension editing the DOM. Render anything environment-dependent after mount, generate IDs with the framework's stable ID hook, and never branch on typeof window inside render. |
Our pick
Static or islands for content, streaming SSR for apps, CSR only behind a login
Build content once. Astro 7 prerenders every page, hydrates only the islands you mark, and costs nothing to host at any traffic a content site will see. Reach for ISR only when editors outpace deploys or the archive is too large to rebuild.
Render applications on the server with streaming so the shell paints before slow data arrives, keep client boundaries small, and cache every page that is not personalized - Next.js 16 with Cache Components or SvelteKit 2 with a Cache-Control header both get there. Client-side rendering stays right for the editor, the canvas and the dashboard nobody indexes. Edge rendering and resumability are answers to specific problems, not defaults.
Keep going
The two frameworks that start from opposite ends of this table meet in Next.js vs Astro. The model also decides where you can host: the hosting directory covers static, serverless and edge options, and Vercel vs Cloudflare settles the two most common hosts.
This was question one of six. The framework decision guide asks the other five, the framework roundup has the per-framework cards, and the performance checklist proves the model hit its Core Web Vitals thresholds.