What you'll learn: By the end of this lesson you will be able to describe the main steps in web app architecture (multi-page sites, Ajax, single-page apps, server rendering with hydration, and islands), name the problem each step solved and the new cost it created, and use two questions, where the HTML is made and who handles navigation, to place any app you meet.
A restaurant can serve a full set menu each time you sit down, or bring one dish at a time to the same table. The first is simple. The second feels faster once you are seated, but the staff must now track the whole meal. Web architecture has swung between the two.
This lesson is the map. Rendering Strategies treats CSR, SSR, SSG, ISR and streaming in depth, so each gets only a working definition here. The running example is a grocery storefront.
Verified against MDN, web.dev, Hotwire Turbo, Astro and React documentation on 2026-11-08.
The core idea
Every architecture below answers two questions: where is the HTML produced, and who owns navigation, the browser or your JavaScript? Each step in the history changed one answer to fix a pain, and paid for it elsewhere. Three labelled illustrations follow: a minimal example (a hand-made router), a realistic example (the storefront in "In practice") and a failure example (a hydration mismatch).
Step 1: the multi-page application
In a multi-page application (MPA) every link or form submission asks the server for a complete new document, and the browser replaces the old page. MDN's View Transition API page uses exactly this contrast: transitions can animate "between DOM states in a single-page app" or "the navigation between documents in a multi-page app".
Figure 1. A click in a multi-page app and in a single-page app.
How to read this: same click, two branches. The first returns a whole document; in the second the page stays alive and only data travels.
An MPA is simple: the URL is the state, Back works, and each page can be cached or indexed as a document. The cost is that nothing survives a click, so even adding an item to a cart is a full navigation.
Step 2: Ajax
Ajax was the fix. MDN defines it as a technique where a web app "fetches content from the server by making asynchronous HTTP requests, and uses the new content to update the relevant parts of the page without requiring a full page load". It was first built on XMLHttpRequest, which "can be used to retrieve any type of data, not just XML" (MDN). A retrospective on the news site heise reports that Jesse James Garrett named the approach in a February 2005 essay, citing Gmail, Google Maps and Google Suggest. The original essay could not be opened for this lesson, so treat the date as reported, not primary.
Ajax added partial updates to the MPA, and created a new pain: content changed while the address did not, so the URL stopped describing what the user saw.
Step 3: the single-page application
A single-page application (SPA) pushes Ajax to its limit. MDN defines it as a web app that "loads only a single web document, and then updates the body content of that single document via JavaScript APIs such as Fetch". The History API repaired the URL: pushState() adds a session-history entry, replaceState() updates the current one, and on Back the browser fires popstate with the saved state.
Minimal example: a hand-made client router
This sketch is illustrative and was not run here. showPage is whatever fetches data and swaps the content.
document.addEventListener("click", async (event) => {
const link = event.target.closest("a[data-spa]");
if (!link) return;
event.preventDefault();
history.pushState({}, "", link.href); // URL must be same-origin
await showPage(link.pathname);
});
window.addEventListener("popstate", () => showPage(location.pathname));
You now own click interception, the address bar, Back, and everything else the browser did for free. MDN names the trade-offs: "SEO, more effort required to maintain state, implement navigation, and do meaningful performance monitoring".
The other cost is the first load: a near-empty shell, then the script, then the data. web.dev's Rendering on the Web warns that for CSR "the amount of JavaScript required tends to grow as an application grows, which can impact a page's INP" (see Core Web Vitals).
Figure 2. The SPA request chain.
How to read this: the shopper sees content only after step 3 finishes.
Step 4: server rendering and hydration
To show content sooner and give crawlers real HTML, teams rendered the same components on a server. web.dev defines SSR as "rendering an app on the server to send HTML, rather than JavaScript, to the client". Hydration then runs client scripts "to add application state and interactivity to server-rendered HTML". In React, hydrateRoot attaches to server-produced HTML and takes over managing the DOM inside it.
Figure 3. Server rendering, then hydration.
How to read this: content appears after the first reply; between the two notes the page looks ready but does not respond.
This fixed the blank first screen and restored a document per URL. The cost is hydration: web.dev notes it can improve first paint but "may have a significant negative impact on TBT and INP", and pages can look ready before they respond. It also adds a rule: React expects the first client render to be "identical with the server-rendered content".
Step 5: less JavaScript, and back to HTML
The latest steps shrink what the browser must run.
- Islands. Astro describes an island as "an enhanced UI component on an otherwise static page of HTML"; client JavaScript is removed unless you opt in per component.
- Server Components. React documents components that render "ahead of time, before bundling"; the client sees only the output, and libraries used there stay out of the bundle. Stateful parts are marked
"use client". - HTML over the wire. Hotwire Turbo intercepts same-domain links and forms and swaps in HTML the server returns: "there's no client-side router to maintain".
- Better MPAs. The Speculation Rules API can prefetch or prerender likely next pages ("near-instant"), and cross-document view transitions, once "impossible" per MDN, animate between pages. MDN marks the first experimental and not Baseline; check support for both.
Figure 4. A product page split into server HTML and islands.
How to read this: only the two islands in the zone ship code and hydrate.
Trade-offs at a glance
| Architecture | HTML made | Navigation owner | Strength | Cost |
|---|---|---|---|---|
| MPA | Server, per page | Browser | Simple, URL is state | State lost on each click |
| SPA (CSR) | Browser | Your JavaScript | App-like feel | Router and state to maintain, first-load JS |
| SSR + hydration | Server, then browser | Either | Early content, real HTML | Hydration cost, mismatch bugs |
| Islands, Server Components | Server, plus small client parts | Usually server or framework | Less client JavaScript | A framework boundary to learn |
| HTML over the wire | Server | Browser plus a small library | No router in your code | Server round trip per interaction |
In practice: splitting a storefront and its order dashboard
This is a realistic scenario, not a case history. It is a composite, it does not describe any real company, and every figure is illustrative.
The situation
A grocery storefront and a staff order dashboard share one client-rendered SPA. Product pages are slow on mid-range phones and search results are thin. The dashboard is a long-lived tool full of live state, and it works well.
Starting architecture
One bundle serves both audiences: every visitor downloads dashboard code, and every product page is an empty shell until its data arrives.
Options considered
| Option | Fixes | Costs |
|---|---|---|
| One SPA plus code splitting | Smaller first download | Still a blank shell per product |
| Server-render everything and hydrate | Real HTML on every route | Hydration cost, even for the dashboard |
| Split: server-rendered product pages with islands, SPA kept for the dashboard | Each side gets the model it suits | Two build targets, one design system to keep aligned |
The decision
The split. Product pages render on the server and hydrate two islands (Figure 4). The dashboard stays an SPA behind sign-in, where SEO is irrelevant and state is the point. Moving between them is a normal link, a document load.
Rollout and verification
Move one category first. Compare field loading and interaction metrics with the old pages, confirm each product URL returns real HTML to a plain fetch, and check Back and deep links.
The 2 a.m. failure
Hydration warnings appear and interaction metrics worsen on some phones. The cause is a "recently viewed" strip inside a typeof window !== 'undefined' branch: the server draws nothing, the client's first render draws items, so the trees differ. React's documentation lists this pattern as a mismatch cause and warns that in the worst case event handlers attach to the wrong elements. First checks: read the development warning, diff server HTML against the first client render, and search render code for window, dates and random values. Mitigation: render the strip after mount, in an effect. Follow-up: a test that renders on the server and compares with the first client render.
Architect's notes
- Choose per route, not per company. A catalogue, a checkout and a dashboard rarely want the same model.
- Count what an SPA takes over from the browser: scroll, focus, history, error pages.
- Send less JavaScript before sending it faster (code splitting), and compare options with Trade-off Analysis.
Common mistakes
- Treating history as a ladder. MPAs are still shipped, and newer designs borrow from them.
- Changing the DOM on navigation without managing focus and the page title.
Key takeaways
- Two questions place any web app: where the HTML is produced, and who handles navigation.
- Ajax and SPAs fixed state loss and moved routing and state into your code.
- SSR and hydration fixed the blank first screen at the price of hydration work and a match rule.
- Islands, Server Components and HTML over the wire send less JavaScript by default.
Practice exercises
Exercise 1 (20 min). Pick three sites you use and say where each produces its HTML and who handles navigation.
Exercise 2 (45 min). Extend the minimal router so Back restores scroll position, and list what an MPA gives free.
Related reading
- Rendering Strategies: each strategy in depth.
- How a Browser Actually Works and The Request Lifecycle
- Core Web Vitals: measuring each step.