The App and the Method
What DevTools is (the browser showing you its own pipeline, one panel per stage), the companion lab app and its routes grouped by stage, and the six-step method every part of this course runs: reproduce, record, read, name the stage, change one thing, measure again.
What DevTools is
DevTools is the browser showing you its own pipeline. The Browser course drew that pipeline stage by stage: network, parse, style, layout, paint, composite, with the event loop running it and the heap underneath. Every DevTools panel is a window onto one of those stages, and reading a panel well is reading the mechanism behind it. Network shows the socket and the caches. Elements shows the DOM the parser built and the cascade as it argued. Performance shows the main thread as time. Memory shows the heap as objects and what retains them. Application shows what persists. Sources shows the code as it runs.
- Look before you guess. A symptom ("it is slow", "it jumps", "it grows") names nothing; a recording names a stage, a function, a selector, a request. The theory comes after the recording.
- Know which stage you are looking at. A 300 ms task in Performance could be script, style, layout or paint; the flame chart under it says which, and the stage decides the fix.
- Change one thing and measure again. The number before and after is the proof. "It feels faster" is a feeling.
The companion app and its routes
The lab app at lab/dist/ is one React app with a route per pathology. Every page says which panel to open and what to look for, then gives controls that trigger the thing and, usually, toggles that fix it one cause at a time. The routes are grouped by the stage they belong to, which is also how the Browser course referenced them.
| area | routes | what they trigger |
|---|---|---|
| parse | broken-markup, stream-vs-buffer ●, sync-script-in-head ● | tree-builder repairs; first paint under streaming; a parser-blocking script against the preload scanner |
| style | font-display, hover-container, invalidation-set, theme-toggle | font loading behaviour; a hover that recalcs 2,000 cells; descendant invalidation; a whole-page recalc |
| layout | thrash, content-visibility, containment | forced synchronous layouts; skipped off-screen layout; scoped layout |
| paint | hover-shadow, layer-explosion, non-passive-scroll, transform-vs-left | repaint regions; layer memory; compositor-blocked scroll; main-thread animation |
| loop | long-task, ordering, passive, timers-background | a 2 s task versus chunked; task/microtask/rAF order; passive listeners; throttled timers |
| js | detached-list, listener-leak, sw-lifecycle, worker-clone-vs-transfer | detached DOM through a Map; a listener retaining nodes; install/waiting/activate; structured clone versus transfer |
| storage | cookie-matrix ●, idb-await-trap, localstorage-cost, quota | SameSite behaviour; TransactionInactiveError; a synchronous 4 MB read; QuotaExceededError |
| security | cors-playground ●, csp-report-only ●, csrf ●, isolation | every CORS failure reason; report-only versus enforce; a cross-site POST under Lax; COOP/COEP |
| nav | bfcache, lifecycle-log, slow-server ●, spa-router | bfcache blockers; freeze and discard events; a 1.5 s TTFB and prerender; an SPA router missing title, focus, scroll, announce |
| input, images, media, text | div-button, tap-sequence, images/lcp, media/decode, text/fallback | a div versus a button in the a11y tree; the tap event sequence; three hero strategies; decoder capabilities; per-script font fallback |
| network | cold-vs-warm ●, no-cache-everything ●, third-party | cache directives against the Size column; 304 storms; a cross-origin handshake and preconnect |
| perf | cls-hunt, inp-phases, lcp-phases, rum | five attributable shifts; input, processing and presentation delay; the four LCP phases; web-vitals attribution |
- Static: most routes work from the built app served by Learna. Open any route from a chapter's lab block or from the app's sidebar.
- With the server (● routes):
cd modules/devtools/lab && node server.mjsserves the app onlocalhost:8921with the endpoints that need real HTTP (streaming, cache headers, a slow TTFB, CSP, no-store) and a second origin on:8922for CORS, CSRF and cross-site cookies. - Throttle: on a fast laptop, set CPU throttling to 4× or 6× in the Performance panel's gear so the pathologies are visible; most were sized for that.
- Device mode (⇧⌘M) for the touch routes; a screen reader (VoiceOver ⌘F5, or NVDA) for the accessibility ones.
The method: look before you guess
- Reproduce on demand with the panel open. If it is one user's device, have them record and export (a Performance profile is a JSON file; a HAR is a Network log); you can load both.
- Record with the panel for the stage you suspect, or Performance when you do not know. It is the panel most pathologies end up in because it shows the whole main thread.
- Read before theorising: the longest thing, its track, what is under it in the flame chart, what the user was doing (Interactions, screenshots). Write the number down.
- Name the stage: style, layout, paint, composite, script, network, memory. The stage picks the course part with the mechanism and the family of fixes.
- Change one thing. Two changes that help is one superstition kept forever.
- Measure again under the same conditions and compare the number. Keep both traces: they are the review and the documentation.
- Optimising the wrong stage: memoising React components when the 300 ms is a Recalculate Style from a theme toggle.
- Measuring on the wrong machine: a 16-core laptop hides a 200 ms problem that a mid phone shows every time. Throttle, or get the field trace.
- Trusting the feel: a change that reorders work can feel faster while the total is unchanged or worse; the Interactions track and INP do not have feelings.
- Reading bar length as cost: in Network, a long bar can be queueing, not transfer; in Performance, a wide task can be idle waiting inside an event handler. The breakdown under the bar is the cost.
- Open
/loop/long-taskwith the Performance panel recording. Click the 2 s button, stop, and before reading anything else find the longest bar on the main thread and what is under it. Then click the chunked button and record again. Write both INP numbers from the summary. That is the loop once, on something real.