awesome-everything RU
↑ Back to the climb

Frontend Architecture

Reviewing a frontend''''s architecture: the order, and the cascading failures

Crux A senior reviews architecture bottom-up — state shape first, build pipeline last — because the early layers cascade. Bad state shape causes re-render storms no code-splitting can fix; the layers interact, so the review order is the lesson.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at junior altitude — the surface
◷ 17 min

A team escalates: the dashboard janks on every keystroke, so they file a ticket to “add code-splitting and lazy load the charts.” You profile it. The charts are not the problem. Form keystrokes are written into a global store, every subscriber re-renders on every character, and the chart subtree is one of them. Splitting the chart bundle changes nothing — the re-render fires after the chunk has loaded. The fix was three units back: keystrokes are local UI state, they never belonged in the global store. The team optimised the last layer to paper over a first-layer bug.

The whole track is one decision tree

Each unit so far taught a decision in isolation: state shape, data fetching, accessible forms, design tokens, monorepo boundaries, code splitting, build pipelines. In a real app they are not seven separate choices — they are one stack, and the stack has a direction. Lower layers constrain upper ones. State shape decides your re-render blast radius; tokens decide whether a rebrand is a config change or a migration; monorepo boundaries decide your CI time; code splitting and the build pipeline decide what actually ships to the browser.

The senior move when reviewing a new frontend is not to read it top-down (file structure, then components, then state) but bottom-up by failure cost. The cheapest bugs to fix are in the pipeline; the most expensive are in the state shape, because everything downstream inherits them. So you evaluate the layers in the order where an error does the most damage, and you stop optimising a layer the moment you realise the real bug lives below it.

The cascade: where each layer’s failure surfaces

The trap in the Hook is the defining pattern: a symptom appears in one layer, but the cause is in a lower one. Re-render jank looks like a rendering or bundle problem, so teams reach for memoization and code-splitting — the upper layers — and burn weeks. The cause was a state-shape decision: high-frequency state (keystrokes) hoisted into a global store, so every subscriber re-renders on every change. No amount of React.lazy fixes that, because the wasted render runs after the chunk arrives.

Read the cascade the other way too. A missing token system surfaces as rebrand pain: a marketing rebrand that should be a one-line swap of primitive values becomes a hunt-and-replace through hundreds of files. Atlassian and every mature design system structure tokens in three layers — primitives (the raw palette), semantics (what a color means: text-danger, surface-raised), and component values. Rebrand = swap primitives. Dark mode = swap semantics, components untouched. Teams that hardcoded #1a73e8 everywhere instead pay weeks; one documented 200-component dark-mode migration took two days with a semantic token layer and would have taken weeks of find-and-replace without it.

Layer (review order)Symptom it produces when wrongWhy no upper layer can fix it
1. State shapeRe-render storms, drifting derived values, stale-after-mutation viewsCode-splitting/memo run after the wasted render; the blast radius is set by where state lives
2. Data fetchingRequest waterfalls, slow LCP, double fetchesA faster bundle still serialises N round-trips if the fetch graph is sequential
3. TokensRebrand pain, inconsistent UI, dark-mode rewriteHardcoded values aren’t a build problem; the source of truth is missing
4. Monorepo boundariesBuild-time explosion, cross-package coupling, slow CIBundlers obey your dependency graph; bad boundaries rebuild everything on any change
5. Code splittingHuge initial bundle, slow Time-to-InteractiveOnly fixable here — but only if layers 1–4 are sound
6. Build pipelineDead code shipped, no tree-shaking, slow CI feedbackCheapest to fix; a config change, not an architecture change

Monorepo boundaries are the build-time multiplier

The layer most often blamed for slow CI — “the build is slow, buy faster runners” — is usually a boundary problem, not a hardware problem. If your packages have no real boundaries (everything imports everything, one giant shared package every app depends on), then any change invalidates the cache for the whole graph and CI rebuilds everything. The fix is structural: clean package boundaries plus affected-only execution, so a one-package change builds one package.

The numbers are stark. Mercari reported a ~50% cut in Turborepo task duration and ~30% in total CI job time after adding remote caching and tuning their workflow. Documented GitHub Actions monorepo setups hit a 12× CI reduction by combining affected-only execution, remote cache, and a dynamic matrix; a 15-minute build drops to 2–3 minutes when only one package changed and the rest are cache hits, and remote caching can eliminate 80–90% of total build time for a large team because every CI run shares work instead of recomputing it. But the biggest lever is upstream of any cache: affected-only execution. Running 4 packages instead of 45 beats any caching optimisation, and you only get there if the boundaries are real. A faster bundler can’t rescue a dependency graph where every leaf depends on the trunk.

Why this works

Why review bottom-up instead of by file structure? Because the layers form a dependency chain, and a fix applied above the real cause is wasted work that also hides the bug. The Hook team’s “add code-splitting” ticket would have shipped, the chart chunk would have lazy-loaded, the jank would have remained, and the postmortem would have blamed the wrong layer. Reviewing in failure-cost order means you find the lowest broken layer first and fix there — so the symptom and every other symptom it spawned disappear at once.

The build pipeline is the cheapest layer — fix it last, not first

The pipeline (bundler config, tree-shaking, minification, the CI integration) is where teams love to start because it feels measurable: turn on a flag, watch a number drop. Chrome DevTools’ Coverage panel routinely shows 20–30% of shipped JavaScript never executes on a given page — real, recoverable bandwidth and parse time. That’s worth doing. But it’s the last thing a senior reviews, because it’s a config change, not an architecture change, and config is cheap to fix later. Optimising the pipeline while the state shape is broken is the engineering equivalent of waxing a car with a seized engine: the surface looks faster, the machine isn’t.

The discipline is to let the failure-cost order drive the review: confirm state shape and the fetch graph (which together own your interactivity and LCP — target LCP under 2.5 s), then tokens (rebrand/theming cost), then boundaries (CI time), and only then split bundles and tune the pipeline. Each upper layer assumes the ones below it are sound; invert the order and you optimise symptoms.

Pick the best fit

A new internal tool: one app, a small design team that rebrands roughly yearly, a 5-engineer team, ~12 packages planned, charts that are heavy but rarely opened. Pick the architecture that fits.

Quiz

A dashboard re-renders on every keystroke in a search box. A teammate proposes code-splitting the charts to fix it. What's the senior read?

Quiz

CI takes 15 minutes for a one-line change in one of 12 packages. What attacks the root cause?

Order the steps

Order the layers a senior evaluates when reviewing a new frontend (most-expensive-to-fix first):

  1. 1 State shape — derived vs stored, server cache vs client state, colocation (sets the re-render blast radius)
  2. 2 Data fetching — waterfalls vs parallelism, what owns LCP
  3. 3 Design tokens — primitive→semantic layering, so rebrand/dark-mode is a swap not a migration
  4. 4 Monorepo boundaries — clean packages + affected-only CI, the build-time multiplier
  5. 5 Code splitting — route/component chunks, lazy-load the heavy and rare
  6. 6 Build pipeline — tree-shaking, dead-code removal, CI integration (cheapest, fix last)
Recall before you leave
  1. 01
    A team blames a janky dashboard on missing code-splitting. Walk through how you'd find the real layer and why their fix wouldn't work.
  2. 02
    Why does a senior review architecture bottom-up by failure cost instead of top-down by file structure, and which layers are most vs least expensive?
Recap

The whole track collapses into one rule: frontend layers cascade, so a senior reviews them bottom-up by failure cost, not top-down by file structure. State shape is most expensive because it sets the re-render blast radius — high-frequency state in a global store causes re-render storms that no code-splitting or memoization can fix, because they run after the wasted render. Above it sits the fetch graph (waterfalls own your LCP), then tokens (a primitive→semantic layer makes a rebrand a value swap instead of a multi-week file hunt — two days vs weeks for a dark-mode migration), then monorepo boundaries (the build-time multiplier; clean boundaries plus affected-only execution and remote cache turned a 15-minute build into 2–3 minutes and cut Mercari’s task time ~50%). Code splitting and the build pipeline come last because they’re the cheapest layers — config, not architecture. The recurring trap is treating a symptom in an upper layer as a bug in that layer; the durable fix is always at the lowest broken layer, and reviewing in failure-cost order is how you find it.

Continue the climb ↑Putting it together: multiple-choice review
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.