awesome-everything RU
↑ Back to the climb

Browser & Frontend Runtime

Five canonical breaks: where production reliably dies

Crux The uncanny valley as the unifying failure, and the five canonical breaks — late hero (LCP), hydration mismatch (CLS), megamorphic path (INP), stale service worker cache (correctness), synchronous handler work (INP) — each owning a different layer.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 22 min

Your team runs a Lighthouse score: 94 green. Then a RUM report lands — field LCP 6.8 s, INP 540 ms, CLS 0.21, all poor. The lab was testing a clean session without the service worker; the field has real users on mid-range phones who tap buttons during hydration. Lab and field are measuring the same page — from different angles, and the field angle is what users live in.

The uncanny valley as the unifying failure.

The single failure that ties the browser chapter together is the gap between “painted” and “interactive” — the hydration uncanny valley. It is not one bug; it is a structural property of server-rendered, client-hydrated apps. Every other failure in this lesson either widens that valley or happens inside it.

The senior framing: a page has two completion moments, LCP-ish (“looks done”) and TTI-ish (“works”), and the entire craft of modern frontend performance is collapsing the distance between them. With that frame, here are the five canonical breaks — the places production most reliably dies along the trace.

Five canonical breaks — layer and metric
Break 1: late hero image
Layers 2+5 → LCP
Break 2: hydration mismatch
Layer 6 → CLS + correctness
Break 3: megamorphic render path
Layers 4+7 → INP
Break 4: stale service worker cache
Layers 1+8 → correctness
Break 5: synchronous handler work
Layer 7 → INP

Break 1 — Late-loaded hero image (LCP).

The hero image is the LCP element, but it is referenced in CSS as a background-image, or it carries loading="lazy", or it is injected by JavaScript. The preload scanner cannot see it, so its download starts late — and LCP, which is measuring exactly that element, lands at 4+ seconds. The trace shows a long resource-load-delay phase.

Fix: the LCP image must be a plain <img> in the initial HTML, eagerly loaded, with fetchpriority="high". This is layer 2 (discovery) failing and layer 5 (paint) paying for it.

Break 2 — Hydration mismatch (CLS plus correctness).

A component renders a value that differs between server and client — a timestamp, a localStorage-derived flag, a locale-formatted price. The server HTML and the client’s first render disagree; React discards the server subtree and re-renders it, flashing changed content and spiking CLS. The trace shows a layout-shift marker right after the hydration long task.

Fix: render determinism — defer client-only values to a post-hydration useEffect. This is layer 6 (hydration) failing because the serialized state was not faithfully reproduced on the client.

Break 3 — Megamorphic render path (INP).

The “Add to cart” handler triggers a re-render, and a hot function on that path — a property accessor, a comparator — is megamorphic: it has seen too many object shapes, so V8 cannot use an inline cache and falls back to slow lookups. The re-render that should take 5 ms takes 80 ms, and INP for the interaction is poor. The trace shows a long scripting bar inside the interaction.

Fix: stabilise object shapes so V8’s inline caches stay monomorphic. This is layer 4 (V8) and layer 7 (the interaction) entangled.

Break 4 — Stale service worker cache (correctness).

A deploy ships new HTML and a new JS bundle, but the service worker serves last week’s cached app.js cache-first against this week’s HTML — module-version mismatch, runtime error. Or worse, a broken app shell is cached and every repeat visitor is trapped on it, bypassing the network where the fix lives.

Fix: content-hashed asset filenames, version-tagged caches cleaned in activate, network-first navigation, a kill switch. This is layer 1 / layer 8 (the worker) failing through statefulness — the cache outlived its correctness.

Break 5 — Synchronous work in an event handler (INP).

The “Add to cart” handler does something synchronous and heavy on the main thread before yielding — fires a synchronous analytics beacon, runs a large JSON.parse, recomputes a derived list. The handler’s processing time alone blows the 200 ms INP budget. The trace shows a long yellow scripting bar between input and the next paint.

Fix: do the minimum to update the UI, then yield (scheduler.yield()), and move pure heavy computation to a worker. This is layer 7 (the interaction) failing on the event loop’s rules directly.

Why each break is mis-diagnosable from one layer.

Break 1 looks like “slow image” to someone who only knows the network — but the image was a normal size; the real cause was discovery, a parse-layer concern. Break 3 looks like “React is slow” to someone who only knows the reconciler — but the reconciler was fine; the real cause was a megamorphic shape, a V8 concern. The whole point of tracing end to end is that the symptom and the cause are routinely in different layers, and only someone who can see the whole chain stops fixing symptoms.

Debug this
log
[TTFB]  2980 ms   (no service worker hit; cold origin)
[FCP]   3240 ms
[LCP]   6800 ms   element: div.hero[style*=background-image]
[INP]   540 ms    interaction: click .add-to-cart
                loaf script: vendor.js (hydration)  482 ms
[CLS]   0.21      source: #price (hydration mismatch)

One RUM session, every metric poor. Walk the chain: name the layer behind each number and the single highest-leverage fix to start with.

Pick the best fit

A product page has good LCP but poor INP for the first few seconds. You have time for one fix this sprint. Pick it.

Which RFC?

Which tool shows the network, main-thread, and GPU work of a page load on one overlapped timeline — the basis for the three-track diagnosis method?

Design challenge

You inherit the e-commerce product page from this trace. Field data: LCP 4.8 s, INP 480 ms, CLS 0.18 — all poor. Design the full remediation plan, layer by layer, in priority order.

  • Mid-range Android, 4G — the real-user p75 segment.
  • Server-rendered React with a service worker already registered.
  • Hero image, price, Add-to-cart, below-the-fold reviews.
  • Must improve all three Core Web Vitals to 'good' at field p75.
  • Each fix must name the layer it targets and the metric it moves.
  • Sequence the work so the highest-leverage fixes land first.
Quiz

Break 1 (late hero) presents as 'slow image' and break 3 (megamorphic path) presents as 'React is slow'. What does this illustrate about tracing?

Quiz

In the RUM log, TTFB is 2980 ms. Why is fixing TTFB the highest-leverage starting point?

Diagnose a slow page by walking the chain

1/3
Edge cases

Break 4 (stale service worker cache) is the break that keeps the worst engineers up at night because it can trap every repeat visitor behind a broken deploy. The service worker’s activate event is the correct place to purge old caches: check the cache version, delete everything that does not match, and only then clients.claim(). Without this, a cached broken shell survives until users manually clear site data — which most users never do.

Recall before you leave
  1. 01
    Name the five canonical breaks, the layer and metric each one hits, and why all five relate to the hydration uncanny valley.
  2. 02
    For each of the five breaks, what is the fix?
  3. 03
    Why does a 2980 ms TTFB make it the highest-leverage first fix even when LCP and INP are also poor?
Recap

The hydration uncanny valley — the structural gap between “painted” and “interactive” — is the unifying failure mode of server-rendered, client-hydrated apps. Five canonical breaks each widen it or happen inside it. Break 1 (late hero, CSS background-image or lazy-loaded) attacks LCP by delaying discovery at the parse layer. Break 2 (hydration mismatch, client-only values rendered on the server) attacks CLS through render non-determinism. Break 3 (megamorphic V8 inline cache, too many object shapes on a hot path) attacks INP by slowing re-renders. Break 4 (stale service worker cache surviving a deploy) attacks correctness by corrupting the state the whole chain depends on. Break 5 (synchronous heavy work in an event handler before yielding) attacks INP by exhausting the processing-time budget. In each case, the symptom names the wrong layer — the diagnosis requires walking the whole chain and finding where the cause actually lives.

Connected lessons
appears again in278
Continue the climb ↑The three-track method: reading traces and building a monitored system
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources5
expand
  1. 01
  2. 02
  3. 03
  4. 04
  5. 05

Trademarks belong to their respective owners. Editorial reference only.