Browser & Frontend Runtime
React Profiler, the Compiler, and production observability
Users report lag. The app feels fine in the office. The Profiler shows a 38 ms commit triggered by a keystroke in a search box — and 240 ProductCards re-rendering with reason “parent rendered, props unchanged.” That is the bug signature. Without the Profiler, you are guessing; with it, the fix is mechanical.
Reading the React Profiler. The React DevTools Profiler records a commit-by-commit timeline. For each commit it shows which fibers rendered, why each rendered (“props changed”, “hook changed”, “parent rendered”, “context changed”), and how long each took. The senior workflow: record an interaction, find the commit that is too long, sort fibers by self-time, and read the “why did this render” reason.
- “Parent rendered” on unchanged props → missing bailout. Wrap in
React.memo, stabilise the offending prop reference. - “Context changed” across many components → unstable context value. Memoise the provider’s value with
useMemo. - “Hook changed” → a
useStateoruseReducervalue that changed. Check if the computation producing it is memoised.
Commit #47 — render: 38.2 ms (1 of 1 committed)
<App> rendered — 0.4 ms — "hook changed"
<Header> rendered — 0.2 ms — "parent rendered"
<Sidebar> rendered — 0.3 ms — "parent rendered"
<ProductGrid> rendered — 36.9 ms — "parent rendered"
<ProductCard> x240 rendered — 0.15 ms each — "parent rendered"
Why did <App> render? -> useState (searchTerm) changed
Note: <Header>, <Sidebar>, <ProductGrid> props unchanged Typing in the search box triggers a 38 ms commit. Header, Sidebar, and all 240 ProductCards re-render with reason 'parent rendered', and the Profiler says their props did not change. What is the fix?
Production observability: LoAF + Profiler. The Profiler gives you local reproduction; Long Animation Frames (LoAF) give you production attribution. When a LoAF entry attributes a slow frame to a React bundle, the Profiler reproduces it locally and names the exact fibers. The two together close the loop from “users report lag” to “this component re-renders 200× because of an inline style object on line 44.”
1. Record an interaction in the Profiler
2. Find the longest commit — sort by total render time
3. Read the “why did this render” reason for slow fibers
4. Fix: “parent rendered” + stable props → React.memo; “context changed” → memoise provider value; state down → collocate state
5. Verify — the fixed commit should show only genuinely-changed fibers
React Compiler and the future of memoisation. For a decade, keeping the bailout firing meant scattering useMemo, useCallback, and React.memo by hand — tedious, error-prone, and itself not free (each memo cell costs memory and a comparison). The React Compiler (formerly “React Forget”, stable-track as of React 19) is a build-time tool that analyses component code and inserts the memoisation automatically and correctly, at finer granularity than a human would. It understands which values depend on which inputs and memoises exactly those, so the bailout fires without any hand-written hooks.
The senior takeaway: the mechanism — referential stability gating the bailout — is not going away; it is the foundation the compiler builds on. Understanding why a stable reference lets a fiber skip work is what lets you read the compiler’s output, debug it when it bails out conservatively, and reason about performance whether the memoisation is hand-written or compiler-generated.
Why this works
Why the virtual DOM is not just “overhead.” “Virtual DOM” is the marketing name for the element tree React diffs. It is worth being precise: the virtual DOM is not faster than direct DOM manipulation — a hand-written, optimal sequence of DOM calls always beats React. What the virtual DOM buys is a programming model: you describe the UI as a pure function of state and never write imperative DOM code, and React makes the result fast enough by diffing. The cost is real — building and diffing the element tree is CPU work that a compiler-based framework (Svelte) or a fine-grained reactive one (SolidJS) avoids. React’s bet is that the developer-experience win of “UI = f(state)” is worth the reconciliation overhead, and that time-slicing keeps that overhead from ever blocking the user.
A search box filters a 5 000-row table. Typing is janky. Pick the primary fix.
Design the rendering strategy for a real-time trading dashboard: a 2 000-row price grid updating ~10× per second, plus interactive filters and a detail panel. It must keep INP under 200 ms and never drop the input box's responsiveness.
- Price ticks arrive ~10 times per second from a WebSocket.
- Only visible rows need to be correct; the grid scrolls.
- Filter and sort controls must feel instant.
- The commit phase must stay short — no long uninterruptible blocks.
- External price store must not tear during concurrent renders.
- INP target: under 200 ms p75.
- Virtualize to cap the commit phase regardless of dataset size.
- useSyncExternalStore for the price store — prevents tearing under concurrent rendering.
- Batch WebSocket ticks to one store flush per frame, not one setState per tick.
- Price grid renders at transition priority; filter input stays in the sync lane.
- Stable symbol-id keys + React.memo + stable props so unchanged rows bail out.
The React Compiler inserts memoisation automatically. Does understanding useMemo and useCallback still matter?
A fiber is where component state lives between renders — the useState values, the refs, the hook list all hang off the fiber, not the component function. Render after render, the fiber persists and carries that state forward; destroy the fiber (unmount, type change, key change) and the state is gone. The reconciler is, at bottom, a machine for deciding which fibers survive and which are rebuilt — the same statefulness question, of what persists across a transition and what is re-derived, that runs through every layer of the stack. ↻ statefulness
- 01A Profiler trace shows 240 ProductCards re-rendering with reason 'parent rendered, props unchanged'. What does that tell you and what do you do?
- 02What is a Long Animation Frame (LoAF) and how does it help diagnose React performance problems?
- 03What is the key insight about the React Compiler that a senior engineer needs to hold onto?
The React DevTools Profiler records each commit with per-fiber render reasons and durations. “Parent rendered, props unchanged” is the mechanical signature of a missing bailout: add React.memo and stabilise unstable prop references with useMemo/useCallback. “Context changed” across many components points to an unstable context value. In production, Long Animation Frames (LoAF) entries attribute slow frames to React code — pair them with the Profiler to name the exact commit and fibers. The React Compiler (stable-track in React 19) inserts memoisation automatically at build time, eliminating the manual scatter of useMemo/useCallback. But the underlying mechanism — referential stability gating the bailout — remains: the compiler builds on it, and you must understand it to debug the compiler’s conservative decisions and reason about performance across any React codebase.
appears again in143
- Why GraphQL gets N+1junior
- DataLoader mechanics: tick-boundary batchingmiddle
- Batch function contracts: ordering, shapes, errorsmiddle
- Federation and lookahead: batching beyond DataLoadermiddle
- Query complexity defences: depth, cost, persisted queriesmiddle
- Senior GraphQL API: scheduling contract, tenant isolation, observabilitysenior
- Why idempotency: making retries safejunior
- Server-side state machine: four states of an idempotency keymiddle
- Outbox and inbox: effectively-once across the dual-write boundarymiddle
- Concurrency and cache architecture for idempotency at scalesenior
- Observability, production failures, and global-scale designsenior
- What is a cache stampede and why it makes things worsejunior
- Lock and single-flight: bounding concurrent rebuildsmiddle
- XFetch: coordination-free probabilistic early expirationmiddle
- Stale-while-revalidate and CDN request coalescingmiddle
- Detecting stampedes and designing TTL for productionmiddle
- Metastable failure, fencing tokens, and production postmortemssenior
- What a relation is: tables, rows, keys, and constraintsjunior
- Constraints, keys, and Postgres data typesmiddle
- Normal forms, denormalization, and why schemas stickmiddle
- JSONB, arrays, and when a side table winsmiddle
- Heap storage, TOAST, and column alignmentsenior
- Schema integrity: deferral, versioning, and production failure modessenior
- Relational vs document, wide-column, graph, and key-valuesenior
- Index-only scans, the Visibility Map, and INCLUDEsenior
- Production failure modes and the index audit playbooksenior
- pg_statistic, ANALYZE, and production observabilitymiddle
- Production failure modes and plan stabilitysenior
- MVCC: why readers and writers never wait for each otherjunior
- Row versions and snapshots: the on-disk mechanicsmiddle
- HOT updates and isolation levels: what you gain and what you paymiddle
- Vacuum and bloat: keeping the storage tax boundedmiddle
- CLOG, XID wraparound, and MultiXact: deep visibility internalssenior
- SSI internals and production autovacuum tuningsenior
- Real-world MVCC failures, deployment patterns, and distributed snapshotssenior
- Connection pools: amortising the cost of a Postgres backendjunior
- PgBouncer session, transaction, and statement modesmiddle
- Pool sizing: the (cores × 2) + spindles formula and the two-layer stackmiddle
- Pool exhaustion and idle-in-transaction: the 3 AM failure modemiddle
- Migrating to transaction mode: rollout playbook and PgBouncer 1.21 prepared statementsmiddle
- The Postgres process model and why raising max_connections degrades throughputsenior
- Pooler landscape 2026, serverless connection storms, and the full failure-mode taxonomysenior
- What a schema migration is and why it replaces ad-hoc DDLjunior
- ADD COLUMN: instant in PG 11+ vs rewrite in older Postgresjunior
- The lock-queue failure mode: why instant DDL can freeze the databasemiddle
- Safe DDL patterns: NOT VALID, CONCURRENTLY, and unsafe-op fixesmiddle
- Expand-contract: zero-downtime for breaking schema changesmiddle
- Advisory locks, migration tools, and deploy coordinationsenior
- Migration failure taxonomy and production disciplinesenior
- Why sharding exists: the single-Postgres ceilingjunior
- Shard-key selection: hash, range, list, and directory strategiesmiddle
- Partitioning vs sharding: same word, two different thingsmiddle
- Co-location and Citus: the invariant that makes sharding usablemiddle
- The hot-shard failure mode: detection, isolation, and durable policymiddle
- Schema-based sharding and multi-tenancy alternativessenior
- Online resharding, 2PC, and the operational cost of shardingsenior
- The seven acts: from CREATE TABLE to Citusjunior
- Acts 1–3 in depth: schema, indexes, and planner statisticsmiddle
- Acts 4–6 in depth: MVCC bloat, connection pooling, and safe migrationsmiddle
- Act 7 in depth: sharding, co-location, and the seven-tier tradeoff cascademiddle
- Observability, anti-patterns, and production triagesenior
- Raft roles, terms, and why majority quorums prevent split brainjunior
- How Raft replicates a log entry and decides it is safe to commitmiddle
- Raft leader election: timeouts, voting rules, and the four safety propertiesmiddle
- Raft in the real world: partitions, slow disks, and client routingmiddle
- Raft extensions: pre-vote, learners, snapshots, and linearizable readssenior
- Raft in production: membership changes, Multi-Raft, and observabilitysenior
- Where data fetching happens — and why it decides LCPjunior
- Fetch waterfalls — diagnosis and the Promise.all curemiddle
- React Server Components and Suspense streamingmiddle
- Client-side cache: TanStack Query, SWR, and stale-while-revalidatemiddle
- LCP, prefetch, and race conditions in interactive fetchingmiddle
- Senior internals: RSC payload, caching layers, and production failure modessenior
- The three-way handshakejunior
- Sequence numbers and connection statemiddle
- DNS: what it does and why it existsjunior
- The resolver walk: referrals, record types, and gluemiddle
- TTL, caching, and DNS propagationmiddle
- The 1-RTT handshake: key shares and ECDHEmiddle
- Session resumption and 0-RTTmiddle
- WebSocket: the HTTP upgrade handshakejunior
- WebSocket frame format: opcodes, masking, fragmentationmiddle
- WebSocket backpressure: when clients can''''t keep upmiddle
- Reconnection: jittered backoff, thundering herd, message resumptionsenior
- WebSocket at scale: HTTP/2 multiplexing, permessage-deflate, C10Msenior
- WebSocket in production: proxies, security, and distributed architecturesenior
- What reverse proxies dojunior
- Health checks, connection draining, and slow startmiddle
- Session affinity, consistent hashing, and the right fixmiddle
- Retry storms, circuit breakers, and load sheddingsenior
- Resilient LB architecture: anycast, zone-aware routing, and observabilitysenior
- Why QUIC and not TCP+TLSjunior
- Connection IDs and network migrationmiddle
- 0-RTT resumption and packet encryptionsenior
- DDoS: what it is and why it worksjunior
- Amplification attacks and state exhaustionmiddle
- Rate limiting: algorithms and architecturemiddle
- WAFs, firewalls, mTLS, and HSTSmiddle
- DNS cache poisoning and BGP hijackingsenior
- Defense-in-depth architecture and attack economicssenior
- DNS, TCP, TLS in sequence: where the milliseconds gomiddle
- Proxy intercepts and security gates: rate limiters, WAF, mTLSmiddle
- Alternate paths: QUIC 0-RTT, WebSocket upgrade, connection migrationmiddle
- Observability: distributed traces, USE/RED, and samplingsenior
- Resilience: cascading retries, circuit breakers, and error budgetssenior
- What the three signals are: logs, metrics, and tracesjunior
- Why structured logs exist: the diary vs the spreadsheetjunior
- The production log schema: fields every line must carrymiddle
- PII redaction and log injectionsenior
- OTel Logs Data Model and audit logs as a subsystemsenior
- SLI, SLO, and the error budget: reliability by the numbersjunior
- Error budget policy, latency SLOs, and composite journeysmiddle
- Production SLO failures, self-observability, security, and the big picturesenior
- The incident loop: from pager to postmortem to preventionmiddle
- Cache lines, struct layout, and false sharingmiddle
- SIMD, SoA vs AoS, and memory bandwidthmiddle
- Cache-oblivious algorithms, PGO, and production failuressenior
- GC in production: observability, security, edge cases, and fleet governancesenior
- Batching: amortize fixed cost per operationjunior
- The batching window: size and wait timemiddle
- Batching in Kafka and Postgresmiddle
- io_uring and observability of batchingmiddle
- From Nagle to io_uring: evolution of batchingmiddle
- Backpressure, failure isolation, and batch security in productionsenior
- CI enforcement and RUM: making budgets stickmiddle
- V8 JIT pipeline, HTTP priorities, and bundle securitysenior
- The performance loop: discipline, not a projectjunior
- Classify and fix: matching bottleneck families to remediesmiddle
- Observability stack and CI gates: catching regressions before they shipmiddle
- Incident to enforcement: SLO burn to verified fix in 35 minutesmiddle
- Culture, economics, and org-scale performancesenior
- At-most-once, at-least-once, exactly-once: the three delivery contractsjunior
- The three failure legs — where duplicates and losses actually happenmiddle
- Consumer-side dedup: the cheapest path to exactly-once processingmiddle
- Kafka exactly-once semantics: idempotent producer and transactionsmiddle
- SQS visibility timeout, DLQ, and the outbox patternmiddle
- Exactly-once in production: impossibility proof, hybrid patterns, and real incidentssenior
- What OAuth is and why passwords are not the answerjunior
- Authorization code flow with PKCEmiddle
- ID token validation and JWKS cache managementmiddle
- Refresh token rotation and scope-based least privilegemiddle
- Sender-constrained tokens: DPoP and mTLSsenior
- OAuth in production: audience attacks, observability, and real failuressenior