
The RenderingPipelineHas No Secrets.
This month: every decision React makes between your JSX and the DOM — reconciliation heuristics, fiber scheduling, concurrent features, and why your profiler numbers still don't tell the full story.
What's Inside
Plus: 3 annotated code walkthroughs, 2 architecture diagrams, and one very opinionated take on React Server Components.
"The layout phase isn't where your slowdown lives. It's the three forced reflows you triggered before you got there."
What the Browser Actually Does
Most engineers have a working model of the browser's rendering pipeline. Parse HTML → build DOM → apply styles → layout → paint → composite. It's accurate enough to build products, and imprecise enough to produce performance bugs that take days to diagnose.
The model fails at the boundaries. When you call getBoundingClientRect() inside a scroll handler, you're not reading a cached value — you're synchronously forcing layout recalculation on whatever subtree the browser considers dirty. The cost isn't the function call. The cost is everything that happens before the function can return.
Understanding the pipeline at this level of specificity changes how you read profiler output. The flame chart stops being a timeline of function calls and starts being a map of causality. You stop asking "what's slow" and start asking"what forced this."
<span class="code-token-comment">// ❌ Triggers layout recalculation on every scroll tick</span>
<span class="code-token-keyword">function</span> <span class="code-token-fn">onScroll</span>() {
<span class="code-token-keyword">const</span> items = document.<span class="code-token-fn">querySelectorAll</span>(<span class="code-token-string">'.item'</span>);
items.<span class="code-token-fn">forEach</span>(item => {
<span class="code-token-keyword">const</span> rect = item.<span class="code-token-fn">getBoundingClientRect</span>(); <span class="code-token-comment">// forced reflow</span>
item.style.transform = <span class="code-token-string">`translateY(${rect.top * 0.1}px)`</span>; <span class="code-token-comment">// invalidates layout</span>
});
}
<span class="code-token-comment">// ✓ Read all, then write all — one layout pass</span>
<span class="code-token-keyword">function</span> <span class="code-token-fn">onScrollOptimized</span>() {
<span class="code-token-keyword">const</span> items = document.<span class="code-token-fn">querySelectorAll</span>(<span class="code-token-string">'.item'</span>);
<span class="code-token-keyword">const</span> rects = [...items].<span class="code-token-fn">map</span>(el => el.<span class="code-token-fn">getBoundingClientRect</span>());
items.<span class="code-token-fn">forEach</span>((item, i) => {
item.style.transform = <span class="code-token-string">`translateY(${rects[i].top * 0.1}px)`</span>;
});
}engineers read last month's issue on partial hydration
Issue 13 · January 2026 · The most-shared Frontmatter issue to date
Fiber Is Not a Data Structure
It's a scheduling protocol. Understanding this changes everything about how you reason about component re-renders, priority queues, and why startTransition does what it does.
React 18's concurrent features aren't a new rendering engine. They're a new scheduling layer on top of the same fiber work loop that shipped in React 16. The distinction matters because it tells you exactly what concurrent mode can and cannot do.
The render phase — where React calls your component functions and diffs the virtual DOM — has always been pure computation. Fiber made this phase interruptible by turning the recursive tree walk into an iterative loop with explicit yield points. startTransition marks work as low-priority so the scheduler can yield to higher-priority updates mid-traversal.
The commit phase — where React mutates the DOM — has never been interruptible and never will be. Once React starts committing, it runs to completion. This is not a limitation. It's a correctness guarantee. Understanding this boundary is the difference between reaching for concurrent features appropriately and being surprised when they don't solve your problem.
"Concurrent mode doesn't make React faster. It makes React smarter about which work to do first."
The remaining 18 pages cover exactly how the scheduler prioritizes work, what happens when you nest transitions, and a complete annotated walkthrough of the reconcileChildFibers source.
of subscribers say Frontmatter changed how they approach architecture decisions
Subscriber survey · December 2025 · 1,204 respondents
"The flame chart is a post-hoc rationalization. You're looking at what happened, not at why it was allowed to happen."
Profiling Without Illusions
React DevTools Profiler shows you component render times. It does not show you why those components rendered, what caused the render to be scheduled, or whether the render was necessary. Three different questions with three different answers, and the tool answers none of them directly.
The useful signal is in the Interactions panel, which most engineers ignore. An interaction is a causal trace — it connects a user event to every render it triggered, across the entire component tree, including deferred work. This is where you find the renders you didn't know you were doing.
The second illusion is self-time. A component with 0.3ms self-time looks cheap in the profiler. But if it renders 847 times in a single interaction, the cumulative cost is 254ms — enough to miss the 16ms frame budget 15 consecutive times. Self-time without render count is meaningless.
The third illusion is the most dangerous: profiling under DevTools in development mode. React development builds include extra validation passes, double-invocation of render functions, and synchronous error boundary handling that doesn't exist in production. Your profiler numbers in dev are structurally different from production behavior, not just slower.
<span class="code-token-comment">// Trace renders with causality, not just duration</span>
<span class="code-token-keyword">import</span> { unstable_trace <span class="code-token-keyword">as</span> trace } <span class="code-token-keyword">from</span> <span class="code-token-string">'scheduler/tracing'</span>;
<span class="code-token-keyword">function</span> <span class="code-token-fn">handleSearch</span>(query: <span class="code-token-type">string</span>) {
<span class="code-token-fn">trace</span>(<span class="code-token-string">'search-input'</span>, performance.<span class="code-token-fn">now</span>(), () => {
<span class="code-token-fn">setSearchQuery</span>(query);
<span class="code-token-fn">setPage</span>(<span class="code-token-num">1</span>);
<span class="code-token-fn">clearFilters</span>();
});
<span class="code-token-comment">// Now DevTools Interactions shows every render</span>
<span class="code-token-comment">// caused by this single user action — across</span>
<span class="code-token-comment">// all three state updates, batched or not</span>
}
<span class="code-token-comment">// Production-accurate profiling via web-vitals</span>
<span class="code-token-keyword">import</span> { onINP } <span class="code-token-keyword">from</span> <span class="code-token-string">'web-vitals'</span>;
<span class="code-token-fn">onINP</span>(({ value, attribution }) => {
<span class="code-token-comment">// attribution.eventTarget tells you exactly</span>
<span class="code-token-comment">// which element triggered the slow interaction</span>
console.<span class="code-token-fn">log</span>(<span class="code-token-string">`INP: ${value}ms on ${attribution.eventTarget}`</span>);
});Browse the Archive
Partial Hydration
Islands architecture, selective hydration, and the cost of JavaScript
State Machines in Production
XState, actor model, and why your useState spaghetti is a finite state machine in denial
The Module Bundler
Rollup, esbuild, Vite — what they actually do and why it matters for your DX
TypeScript at Scale
Compiler performance, declaration files, and the project references you're not using
Not ready to subscribe? Read a full issue free — Issue 12 on state machines is publicly available.
Read the
Full Issue.
32 pages. One concept. No padding. Delivered the first Thursday of every month to engineers who want to understand their tools all the way down.
One email per month. No tracking pixels. Unsubscribe in one click.
Not sure yet? Browse the archive — Issue 12 is free to read in full.