Step 1 of 5 · Reading · ~4 min
Read
Production
Production Frontend
A framework that only runs in the browser leaves users looking at a blank page until its JavaScript has downloaded and executed. Production frontends solve that by rendering on the server first. After this lesson your program does the check at the heart of that approach: it compares the HTML a server sent with the tree the client wants to show, and says whether the client can take over the existing page or must rebuild it.
The production stack in one table
You will not write all of this yourself, and you should not try. Know what each concern is and which tools own it.
| Concern | Common choices |
|---|---|
| UI framework and meta-framework | React with Next.js or Remix, Vue with Nuxt, Svelte with SvelteKit, Solid with SolidStart |
| Bundling and build | Vite, esbuild, swc, Turbopack, webpack (legacy) |
| Types | TypeScript |
| Client and server state | Context, Redux, Zustand, Jotai; TanStack Query or SWR for server data |
| Routing | file-based routers in the meta-frameworks, React Router in single-page apps |
| Styling | Tailwind, CSS Modules, CSS-in-JS |
| Testing | Vitest or Jest, Testing Library, Playwright or Cypress |
| Delivery | Vercel, Netlify, Cloudflare, containers; measured with Lighthouse and Core Web Vitals (LCP, INP, CLS) |
The pragmatic answer to "should we build our own framework?" is no. You build a toy one to understand the internals, then use a maintained one.
Server rendering
With client-side rendering the server sends an empty page and a script; nothing is visible until the script runs. With server-side rendering (SSR) the server runs the same components, turns the tree into an HTML string exactly as in lesson 1, and sends that: the content shows immediately and search engines can read it. Static generation does the same once at build time so a CDN can serve the result. Streaming sends the page in pieces as parts of it become ready.
Hydration
HTML from the server is inert: it has no event handlers and no component state. Hydration makes it live. The client runs the same render and, instead of creating DOM nodes, walks the existing ones in step with the new tree: it adopts each node and attaches handlers. If server and client agree, no node is created or rebuilt.
server HTML <div id="app"><p class="lead">Hi</p></div>
client tree div (id app) > p (className lead) > text Hi
walk div adopts div, p adopts p, text adopts text: HYDRATED
A mismatch
Hydration only works if both sides produce the same thing. Four kinds of disagreement matter: a different tag, different text, a different attribute, and a different number of children. React reports them in development, and when it cannot trust the markup it throws that part away and renders it again on the client, which costs a flash and a layout shift. Typical causes are values that differ between the two runs (the current time, a random id, a locale or timezone, anything read from window), data only one side has, and HTML the browser reshapes while parsing, such as a div inside a p. The fix is to render the same output on both sides and do client-only work in an effect.
The client tree is compared "as parsed"
What the browser builds from HTML is not always the shape of the tree that produced it. Adjacent text children come out as one text node, and an empty text child is not there at all (React puts a comment marker between texts for this reason). Attribute order is irrelevant, className is the attribute class, and the five entities from lesson 1 are decoded. So the client tree is normalised first, then compared.
Reporting the first mismatch
Compare nodes in document order, using the paths from lesson 5. At each pair:
| Disagreement | Message after the path |
|---|---|
| element on one side, text on the other | kind: expected <p> got text |
| different tags | tag: expected <div> got <span> |
| different text | text: expected "Hi" got "Hello" |
| attribute differs, is missing or is extra | attr class: expected "a" got (none) |
| children differ in number, after the common ones matched | children: expected 3 got 2 |
Common mistakes
- Decoding
&first, so&lt;becomes<instead of the text<. - Comparing attributes in order, or comparing
classNamewithclass. - Counting two adjacent text children as two nodes.
- Treating
checkedandchecked=""as the same. - Reporting the child count before the children that do match.
Your exercise
The starter parses the server HTML into the same kind of tree as the client's and prints your result. You write normalize and hydrate.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…