Skip to content

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.

ConcernCommon choices
UI framework and meta-frameworkReact with Next.js or Remix, Vue with Nuxt, Svelte with SvelteKit, Solid with SolidStart
Bundling and buildVite, esbuild, swc, Turbopack, webpack (legacy)
TypesTypeScript
Client and server stateContext, Redux, Zustand, Jotai; TanStack Query or SWR for server data
Routingfile-based routers in the meta-frameworks, React Router in single-page apps
StylingTailwind, CSS Modules, CSS-in-JS
TestingVitest or Jest, Testing Library, Playwright or Cypress
DeliveryVercel, 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:

DisagreementMessage after the path
element on one side, text on the otherkind: expected <p> got text
different tagstag: expected <div> got <span>
different texttext: expected "Hi" got "Hello"
attribute differs, is missing or is extraattr class: expected "a" got (none)
children differ in number, after the common ones matchedchildren: expected 3 got 2

Common mistakes

  • Decoding &amp; first, so &amp;lt; becomes < instead of the text &lt;.
  • Comparing attributes in order, or comparing className with class.
  • Counting two adjacent text children as two nodes.
  • Treating checked and checked="" 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.

Up nextCapstone: TodoMVC State MachineProduction

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…