Perceived performance lab

Make reading feel fast.
Pay for editing later.

The feature set stays the same. Only delivery changes: one large file, two chunks preloaded together, or a read chunk now and an editor chunk after intent.

Read the full write-up: Bundle for Perceived Performance, Not Just Size ↗

The server streams real production files. It does not use fake loading timers.
Lazy startup reduction
Lazy read improvement Run the race
Feature parity Same editor
Actual build output

Same total capability, different delivery order

Raw minified JavaScript is shown below. Gzip sizes are included in each build card for reference.

Visual race

Watch when useful content appears

All frames use separate cold-cache run IDs and the same network profile.

01

Monolithic bundle

Read + editor on the initial path

Queued
Read ready
Edit wait
Resource waterfall appears after the run starts.
02

Eager split

Read and editor chunks start together

Queued
Read ready
Edit wait
Resource waterfall appears after the run starts.
03

Lazy split

Editor starts after intent

Queued
Read ready
Edit wait
Resource waterfall appears after the run starts.
Repeatable evidence

Remove the lucky run

Run six cold trials for each build. Trials run one at a time, then the dashboard reports the median read time and edit wait.

Ready

1

Monolith

The browser cannot evaluate the app until the read UI, CodeMirror, language modes, and editor feature data have all arrived.

2

Eager split

Both chunks start together. The smaller read chunk can finish and render while the editor continues downloading and compiling.

3

Lazy split

Only the read chunk starts. This minimizes startup contention, but the first edit pays the editor download cost.

Methodology

How this lab measures perceived performance

The goal is a fair, reproducible comparison, not a cherry-picked demo. Here is exactly what is real, what is simulated, and how to verify it yourself.

Real production output

All three builds are ordinary Vite production bundles of the same React app: minified, code-split with React.lazy(), no fake loading timers. The build script fails if the monolith drifts outside 800-1000KB, if the split's initial chunk drifts outside 30-40% of total JS, or if the three builds' feature sets drift by more than 5KB. That keeps the comparison about delivery order, not about one build secretly doing less work.

Deterministic network shaping

A small Node HTTP server enforces a fixed RTT latency, then trickles the response in ticks capped to a per-profile byte-budget: Office Wi-Fi (10Mbps, 30ms), Fast 4G (4Mbps, 80ms), and Slow 4G (1.6Mbps, 160ms). This is server-side shaping applied to real bytes, not a client-side setTimeout.

Every run is genuinely cold

Each run gets a fresh UUID run ID baked into its URL, and every response is sent with Cache-Control: no-store. The browser cannot reuse a previous run's cached bytes, so the cold race, the six-trial benchmark, and the manual profiling links below all start from zero every time.

Two ways to open each build

Every build card has a Throttled link, which opens that build alone under the server-side shaping above, and a Raw link, which serves the same cold build at full local speed with zero server delay. Use Raw when you want to apply Chrome or Firefox DevTools' own network and CPU throttling presets, or to record a Performance trace of parse and compile time without any artificial network delay in the way.

Read-ready is an app mark, not paint

The Read ready metric comes from a performance.mark() the app fires on a requestAnimationFrame after React commits the read view. It is a stable, reproducible application milestone, but it is not the same thing as the browser's Largest Contentful Paint. Treat LCP, visible in each build's own DevTools trace, as the defensible paint-time number when the exact painted frame matters.

Throttled bandwidth is shared

In the cold race, all three builds run at once and share the same simulated aggregate bandwidth, the same way three tabs on one Wi-Fi network would. That contention is intentional: it is why the lazy build, which sends the least at startup, can finish its read chunk sooner than the eager build even though both start downloading together.

Do not stack both throttling layers at once. Opening a Throttled link while DevTools network throttling is also enabled will apply both delays and produce numbers that do not correspond to any real network. Use Throttled links with DevTools throttling off, and Raw links when you want DevTools in control.

For the full measured write-up, including Chromium and Firefox traces and the reconstructed download/parse/compile timeline, see Bundle for Perceived Performance, Not Just Size ↗.