Lakshya

Software Engineering · Chapter 12 of 31

Frontend engineering, past the framework

Where the HTML gets made, why state management is the actual hard problem, and the performance the user feels rather than the one your laptop shows.

9 min read1 diagramAll 31 chapters

Frontend interviews go badly when they turn into a framework quiz, and they go well when the candidate can reason about the three things that are true regardless of framework: where rendering happens, how state is organised, and what makes a page feel slow.

Where the HTML gets made

WHERE IS THE HTML MADE? EVERY FRONTEND ARCHITECTURE ARGUMENT IS THIS QUESTION. CLIENT-SIDE (SPA) first paint blank until JS loads and runs good for app-like tools behind a login cost: slow first load, weak for search engines the browser downloads a framework, then asks for data SERVER-SIDE (SSR) first paint real HTML immediately good for content, commerce, anything indexed cost: a server render per request; TTFB is yours to own then JavaScript attaches — hydration STATIC (SSG) first paint real HTML from a CDN, instantly good for content that changes on a schedule cost: rebuild to change anything the fastest possible answer when it applies STREAMED / PARTIAL first paint shell instantly, regions fill in good for mixed pages: static frame, dynamic parts cost: complexity, and harder mental model the current direction of the whole field HYDRATION IS THE COST NOBODY BUDGETS Server-rendered HTML appears fast — and the page is not interactive until the JavaScript has downloaded, parsed and re-attached toit. So a user sees a complete-looking page, taps a button, and nothing happens. That gap is invisible in a screenshot and very visible ona mid-range phone. It is why the field moved toward streaming, islands and server components: send less JavaScript, and hydrate only the parts that areactually interactive.
Four answers, and the cost each one carries. The hydration note at the bottom is the thing most candidates miss and the reason the whole field moved.

A fully client-rendered application ships a blank page plus a framework, then asks for data. It is the right answer for an application behind a login where the first load happens once a day and everything after it is instant — a dashboard, an editor, an internal tool. It is the wrong answer for anything a search engine or a first-time visitor on a slow connection needs to see.

Server rendering produces real HTML per request, so content appears immediately. The cost moves to your server, and time-to-first-byte becomes yours to own rather than the network's.

Static generation produces the HTML at build time and serves it from a CDN. When it applies, nothing is faster. It applies less often than people hope, because most pages have at least one personalised region — which is exactly what the streaming and partial approaches exist to solve.

Hydration, the cost that is invisible in a screenshot

Server-rendered HTML paints quickly and is not interactive until the JavaScript has downloaded, parsed, executed and re-attached event handlers to the existing markup.

So the user sees a page that looks finished, taps a button, and nothing happens. On a fast laptop this window is imperceptible. On a mid-range phone over a poor connection it can be several seconds, and it is the single most common cause of the gap between ‘our metrics look fine’ and ‘the site feels broken’.

This is why the field moved. Streaming sends the shell immediately and fills regions as they resolve. Islands hydrate only the genuinely interactive parts and leave the rest as plain HTML. Server components run on the server and never ship their code at all. Every one of these is the same idea: send less JavaScript, and hydrate less of the page.

If you can explain that trajectory in an interview — the problem, and why each generation of tooling attacked it — you have said something more useful than naming the current framework.

State, which is the actual hard problem

Most frontend complexity is state complexity, and most of it comes from one mistake: treating all state as the same kind of thing. There are at least four kinds and they want different tools.

KindExamplesWhat it wants
Server stateData fetched from an APIA cache, not a store. It has an owner elsewhere, it goes stale, it needs revalidation, loading and error states. This is most of your state
URL stateFilters, page number, selected tab, search queryThe URL. If it should survive a refresh or be shareable as a link, it belongs there and nowhere else
Local UI stateIs this dropdown open, is this input focusedComponent-local. It should never leave the component
Global client stateTheme, auth session, feature flagsA store — and this is the smallest category, which is the opposite of how most applications are built

The single most consequential realisation in modern frontend work: server state is a caching problem, not a state-management problem. Once you treat it that way — with a library that handles fetching, caching, deduplication, background revalidation and invalidation — the global store shrinks to almost nothing, and a great deal of the boilerplate that frontend architecture arguments are about simply disappears.

And the URL point is underrated. If a user filters a table, refreshes, and loses the filter, that is a bug — and it is a bug caused by putting the filter in a component instead of the query string. Anything a user would expect to be able to send to a colleague belongs in the URL.

Performance the user actually feels

Frontend performance is measured badly almost everywhere, because it is measured on the developer's machine. Three metrics matter and they map to three distinct experiences:

  • How long until I see the main content? Dominated by the largest image or text block on screen. The fixes are almost always: the image is too big and unoptimised, a font is blocking, or a request chain means the browser cannot even start fetching until three other things resolve.
  • How long until I can interact, and does it respond? Dominated by JavaScript execution on the main thread. Long tasks block everything, including scrolling.
  • Does the layout move under me? Caused by images without dimensions, ads and embeds injected late, and fonts swapping. This one is pure irritation and almost entirely preventable with reserved space.

The measurement rule that changes conclusions: test on a mid-range phone on a poor connection, not on your laptop.

A modern development machine has roughly an order of magnitude more CPU than the phone a large share of your users have, and a JavaScript bundle that parses in 200 milliseconds on a laptop can take two seconds on that phone. Throttling in developer tools is a reasonable approximation; a real cheap device is better.

And synthetic tests are not enough. Field data from real users is what reveals the tail — the users on old devices, poor networks, or in regions far from your servers. A synthetic test tells you about one device on one connection, and the distribution is the whole story.

The single largest lever is almost always the JavaScript bundle, and the fixes in order: route-level code splitting so a page ships only what it needs; auditing dependencies for the one that is 300 kilobytes because it does date formatting; and moving work off the main thread or onto the server.

Accessibility, briefly and non-negotiably

Accessibility is treated as a specialism and most of the value is in about six things that cost nothing if done from the start and are expensive to retrofit:

  • Use the right element. A button that is a div with a click handler is not focusable, not keyboard-operable, and not announced as a button. The native element gives you all of that free.
  • Every interactive thing must be reachable and operable by keyboard, in a sensible order, with a visible focus indicator. Removing focus outlines because they look untidy is the most common single accessibility regression.
  • Labels on form inputs, associated properly rather than placed nearby.
  • Contrast that meets the ratio, which is also just legibility — it helps everyone in sunlight.
  • Alternative text that says what the image conveys, and empty alt text for decorative images so a screen reader skips them.
  • Announce dynamic changes. If content appears after an action, a screen-reader user needs to be told; otherwise the page silently changed underneath them.
  • And test with a keyboard and a screen reader occasionally. Ten minutes reveals more than any automated checker, which by construction can only find the mechanically-detectable subset.

What a frontend interview is actually testing

Beyond the coding exercise, the signals that separate candidates:

  • Can you reason about rendering strategy from requirements? Given ‘a public product page that must rank in search and shows live stock levels’, can you choose and justify an approach?
  • Do you distinguish kinds of state? Putting server data in a global store is the tell that someone has not thought about it.
  • Do you know what makes it slow on a phone? Not what makes it slow on your machine.
  • Can you build a component that is accessible without being reminded? A modal that traps focus, closes on escape, and returns focus where it came from is a very good small exercise and a lot of people cannot do it.
  • And can you say what you would not build? Frontend has an unusual amount of optional complexity, and the ability to say ‘this does not need a state library’ is worth more than knowing three of them.

Check yourself

A public product page must rank in search and show live stock. What rendering approach?Server-render or statically generate the page for the indexable content, and fetch the live stock on the client or stream it in. The distinction is what needs to be in the initial HTML.
Where does server data belong?In a cache with revalidation, not in a global store. It has an owner elsewhere and goes stale — treating it as a caching problem shrinks the store to almost nothing.
Why does a server-rendered page sometimes feel broken?Hydration. The HTML paints before the JavaScript has attached handlers, so the page looks finished and does not respond. Invisible on a laptop, seconds on a mid-range phone.
What is the most common accessibility regression?Removing focus outlines because they look untidy — which makes the whole interface unusable by keyboard.
← API design and cachingMobile engineering, and the constraints that shape it →