awesome-everything RU
↑ Back to the climb

Browser & Frontend Runtime

React, Vue, and INP observability in production

Crux How React 18''''s concurrent scheduler and Vue 3''''s microtask batcher map onto the event loop, and how to build an INP root-cause pipeline from LoAF telemetry to deploy.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 15 min

Users report the search bar “feels slow” on your React dashboard. INP reports 380 ms p75. You deploy a fix and INP drops to 90 ms. You do not get paged three weeks later when another engineer’s PR reverses the improvement — because you never wired LoAF telemetry to your CI pipeline.

How React 18 schedules work

React’s scheduler runs reconciliation in cooperative chunks of 5 ms. After each chunk, it yields to the browser using MessageChannel.postMessage — the only cross-browser mechanism that queues a task without the 4 ms clamp of setTimeout. The browser’s loop gets to run input, render, and other tasks; React resumes on the next task to do the next 5 ms chunk.

This is the implementation of “concurrent React”: a single render is split across many tasks, none of which exceeds the 50 ms long-task threshold. useTransition marks a state update as low-priority, allowing input updates to interrupt it. Understand the loop and React’s scheduler stops being mysterious.

React 18 concurrent scheduler details
Work chunk size
5 ms
Yield mechanism
MessageChannel.postMessage (no 4 ms clamp)
useTransition
marks update as interruptible by input
Max task duration
< 50 ms (long-task safe)
Resumption
next task on the event loop

Vue 3’s scheduler

Vue 3 batches reactive updates within a microtask: a write to a ref schedules a microtask that flushes all pending updates at the end of the current task. This means many writes in one task collapse into one update — but it also means the flush is a microtask and can prolong starvation if it triggers more reactive writes.

Vue exposes nextTick(callback) for code that wants to run after the flush; underneath, this is just another microtask scheduled after the flush microtask.

Quiz

React 18 uses `MessageChannel.postMessage` to yield between reconciliation chunks instead of `setTimeout(fn, 0)`. Why?

Quiz

A Vue 3 component writes to three refs in one synchronous block. How many DOM updates result?

Production INP root-cause pipeline

Recipe for INP root-cause in production:

Subscribe to LoAF entries with PerformanceObserver. For each entry, record the firstUIEventTimestamp, the entry’s renderStart, and the array of scripts with attribution. When INP fires for a slow interaction (also via PerformanceObserver), correlate by interaction id: find the LoAF that overlapped the input, attribute the time to the heaviest script, send to telemetry with the script URL and function name (resolved via sourcemap server-side). This pipeline turns “users are complaining about lag” into “deploy 3a4f1c regressed search input by adding a 320 ms reducer call.”

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.entryType === 'long-animation-frame') {
      const heaviest = entry.scripts
        .sort((a, b) => b.duration - a.duration)[0];
      sendToTelemetry({
        frameStart: entry.startTime,
        frameDuration: entry.duration,
        scriptUrl: heaviest?.sourceURL,
        functionName: heaviest?.sourceFunctionName,
        charPosition: heaviest?.sourceCharPosition,
      });
    }
  }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

INP as CI gate. Production telemetry catches regressions after they reach users. A realistic gate: a synthetic Playwright/Puppeteer test in headless Chrome reproduces the critical interaction (type a query in search, open a menu, switch a tab), measures INP via the same PerformanceObserver code as in prod, and blocks merge if p75 crosses the budget. Key detail: headless Chrome on a CI runner is typically faster than a mid-range phone, so without --cpu-throttling-rate=4 (or CDP equivalent) the test passes locally and fails in prod. Bundle-size budgets add a second layer: JS size converts directly to parse+compile time, which converts to long tasks at load. Without all three layers — synthetic INP gate, bundle budget, and production LoAF alerts — a regression slips in silently and lives until the next manual profile.

Quiz

A LoAF entry's `scripts` array shows a 280 ms duration attributed to `reducerRootReducer` in `bundle.js:1:94821`. What is the next step to get a file:line in the original source?

Why this works

The three-layer observability stack — synthetic CI gate, bundle budget, production LoAF alerts — is the engineering analog of the three-layer throttling stack for timers. Both problems have compounding factors that no single instrument catches. A synthetic test catches regressions before users see them but misses device diversity; production LoAF alerts catch what synthesis missed but arrive after real users are hurt; bundle budgets prevent the problem before it exists in code. Without all three, you are optimizing one layer while the others leak.

Quiz

Your CI synthetic INP test passes at p75 = 120 ms. Real-user p75 INP is 480 ms. What is the most likely explanation?

Recall before you leave
  1. 01
    Explain how React 18 concurrent rendering keeps individual tasks under the 50 ms long-task threshold.
  2. 02
    Describe the three-layer INP observability stack and why all three are needed.
  3. 03
    A Vue 3 component writes to 10 refs in a loop. Will there be 10 microtask flushes or 1? What happens if the flush triggers more reactive writes?
Recap

React 18’s concurrent scheduler uses MessageChannel.postMessage to yield between 5 ms reconciliation chunks — a task-level yield with no 4 ms clamp, interleaving React work with browser input and render steps. useTransition marks updates as interruptible, allowing urgent input to cut in front of a slow reconcile. Vue 3 batches reactive updates into a single microtask flush per synchronous block via nextTick, which is efficient but can produce microtask chains if flush callbacks trigger more reactive writes. In production, INP root-cause requires three instruments working together: a LoAF PerformanceObserver that records per-script attribution; a sourcemap server that resolves script URLs and character positions to original file:line; and a CI-gated synthetic INP test with CPU throttling that enforces the budget before regressions reach real users. Without the CPU throttling, CI runners are too fast to catch regressions that only appear on mid-range phones.

Connected lessons
appears again in143
Continue the climb ↑Event loop: 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.