Reading a Network Waterfall
Bars are not the point. The staircase is: a resource that could not start until another finished is a dependency your page structure created, and often one you can delete.
The intent, the obvious build, and why it breaks
Every lesson starts where the work starts: someone wanted an outcome, and the first implementation that comes to mind has a problem.
When I open the Network panel, what am I actually looking for?
Someone reported that the page takes forever. You have a waterfall in front of you and a limited amount of time to name the thing that is wrong.
Find the longest bar. That is the slow resource. Make it smaller or delete it.
The longest bar is very often a large image that started early, finished last, and blocked nothing. Deleting it changes the total and changes nothing the user feels.
- The longest bar is very often a large image that started early, finished last, and blocked nothing. Deleting it changes the total and changes nothing the user feels.
- The bar that matters is frequently short — a 2 KB JSON response that did not start until a 300 KB bundle had parsed, three seconds into the load.
- Two waterfalls with identical total time can describe completely different problems: one is bandwidth-bound with everything in parallel, the other is latency-bound with everything in single file.
- A bar that looks like download time is often mostly queueing. The browser had the request ready and deliberately held it, and no amount of shrinking the file addresses that.
- "The API is slow" is the usual conclusion, and it is usually wrong: the API responded promptly to a request that left far later than it could have.
What is actually happening
In the browser, not in the framework.
- Each row is one request, and each row is segmented: time queued or stalled, DNS resolution, connection establishment, TLS negotiation, request sent, waiting for the first byte, then content download. The segments have different causes and different fixes.
- Queueing is the browser's own scheduling. It holds requests behind higher-priority ones, behind a per-origin connection limit on HTTP/1.1, and behind other work it considers more urgent right now.
- Connection reuse is why the first request to an origin has three setup segments and the next twenty have none. Every extra origin on the page re-pays that cost from scratch (Connection Pooling in Networking).
- Priority is a real thing the browser assigns, not a metaphor: a render-blocking stylesheet outranks a late image, and the browser will delay the image to get the stylesheet sooner. Hints and attributes adjust this assignment (Resource Hints).
- The start time of a bar encodes discovery. A request cannot leave before something told the browser it existed, so a late start is a statement about the page's structure rather than about the network.
- A staircase — each bar starting where the previous one ended — is therefore the signature of a serial dependency chain. Bars that overlap are parallel; bars that step are sequential, and sequential is what latency multiplies.
What this makes the browser do
And which of it is avoidable.
- Maintaining a request queue with priorities, per-origin limits and reprioritisation as the document parses.
- Opening, reusing, and eventually closing connections; on HTTP/1.1 juggling a small pool per origin, on HTTP/2 and HTTP/3 multiplexing streams over one (HTTP/2: Streams on One Connection in Networking).
- Running the preload scanner so that discoverable subresources can leave while the main parser is blocked — visible in the waterfall as requests that start earlier than their position in the document suggests (The Preload Scanner).
- Decompressing and, for images, decoding off the main thread. The download bar ending is not the same moment the resource is usable.
- Recording all of it into the timeline, which itself costs something — a waterfall recorded with devtools open is not a neutral observation of the page.
What each segment of a bar actually is
A single row is not one thing. Before you can say a resource was slow, you have to say which part of it was slow, because the seven phases below have almost nothing to do with each other.
The habit to build is to open the timing breakdown for one request rather than eyeballing the bar. A row that looks like a download problem is frequently a queueing problem, a handshake problem, or a server-thinking problem wearing the same shape.
| Segment | What the browser is doing | What makes it long | What actually helps |
|---|---|---|---|
| Queued / stalled | The request exists but has not been sent | Per-origin connection limits, or a higher-priority request holding the slot | Reduce path-critical requests, or fix priority with the right hint (Resource Hints) |
| DNS | Resolving the hostname to an address | A cold lookup to an origin the page has never used | dns-prefetch or preconnect for an origin you will certainly need (Following One Lookup Through Every Cache in Networking) |
| Connecting | Establishing the transport connection | Round trips, multiplied by distance | Fewer origins; an edge closer to the user (CDN Delivery) |
| TLS | Negotiating the secure channel | More round trips on a cold connection; session resumption removes most of it | Connection reuse — this segment appears once per origin, not once per request (The TLS Handshake in Networking) |
| Request sent | Writing the request | Almost never the problem, except for large uploads | Ignore it unless you are posting a body |
| Waiting | The server is thinking, and the bytes are travelling back | Server work *plus* the network path in both directions | Split the two: compare an edge hit against an origin hit before blaming the backend |
| Content download | Receiving the body | Size, compression, and bandwidth | Compression and payload reduction — the one segment "make it smaller" addresses (Minification Is Not Compression) |
Look for the staircase
Here is the whole lesson in one picture. Nothing in the timeline below is large. Every request is fast. The page is slow, and it is slow because each row could not start until the row above it finished — which is a fact about the page's structure, not about the network.
Trace the causes: the document names the bundle, so the bundle waits on the document; the application inside the bundle asks for the data, so the data waits on parse and execute; the avatars are named inside the data, so they wait on the response. Four sequential round trips to show a list of names. On a fast connection this is invisible. On a cellular one it is the entire experience.
The fix for each step is different, and naming the step is what tells you which fix applies. Server-rendering the first screen removes steps two and three at once; a modulepreload in the document removes the wait at step two only; sending the data with the document removes step three; putting the avatar URLs in the initial markup removes step four (Streaming Server Rendering).
- DNS + connect + TLS — Paid once per origin. Nothing about your code changes it except not needing the origin.
- Document — Streams — the parser starts on the first chunk, not at the end of the bar.
- app.js (named in the document) — Step one of the staircase: could not be requested until the head was parsed.
- Parse + execute app.js — Device-bound, not network-bound. On a slow phone this bar is the widest one on the page.
- GET /api/people (issued by app.js) — Step two: a small response that left very late, which is why "the API is slow" is the wrong conclusion.
- Avatar images (URLs came from the response) — Step three: discovered inside JSON, so no scanner and no hint could have found them.
- Layout shift as avatars land — Unreserved space. The list the user was already reading moves (Visual Stability).
Every bar here is short. The page is slow anyway, because the bars are in single file. That is the difference between a byte problem and a structure problem.
Shapes and what causes them
Once you can see segments and staircases, most waterfalls fall into a handful of recognisable patterns. These are the ones worth being able to name on sight, because each one points at a specific edit rather than at a vague intention to optimise.
The last row is the one people find hardest to believe: a resource can be given a *lower* priority by the very hint that was supposed to speed it up, because a preload with the wrong as value tells the browser the wrong thing about what it is (Resource Hints).
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
| Six requests to one origin start, the seventh waits for one to finish | A neat ladder of bars in groups | HTTP/1.1 per-origin connection limit | Confirm the protocol column before doing anything. On HTTP/2 this pattern should not appear; if it does, something in the path downgraded the connection (HTTP/1.1 vs HTTP/2 vs HTTP/3 in Networking). |
| Long stall while the network is otherwise idle | A wide empty segment before the request is even sent | The browser deprioritised it — typically a late-discovered image or a low-priority script | Decide whether that priority is wrong. Usually the browser is right and the resource genuinely is not needed yet. |
| Every request to a host repeats DNS, connect and TLS | Three setup segments on many rows to the same origin | Connections are not being reused — a Connection: close, an intermediary, or requests spread across sibling hostnames | Consolidate hostnames; check for a proxy terminating connections (Keep-Alive and Connection Reuse in Networking). |
| One slow response delays unrelated ones on the same connection | Several rows finish suspiciously together, late | Head-of-line blocking at the transport layer under HTTP/2 | This is exactly what HTTP/3 over QUIC exists to remove; measure before assuming your version has it (Head-of-Line Blocking in Networking). |
| A request starts far later than the markup that needs it | A late bar with an application script as its initiator | Discovered by code, not by the parser — invisible to the preload scanner | Reference it in the document, or announce it with a hint. This is the staircase, one step at a time. |
A preloaded resource downloads late, or twice | The hint appears to have made things worse | Wrong as value, a mismatched crossorigin, or a URL that does not match the one the page eventually requests | Fix or remove the hint. A wrong preload is a net loss: it costs bandwidth and contention and delivers nothing. |
How to build it
Most important first.
- Read starts before you read lengths. Sort by start time and ask of each row: what told the browser this existed, and could that have happened sooner?
- Find the longest staircase and count its steps. That count is the number of sequential round trips on the path, and it is the number that latency multiplies (The Critical Rendering Path).
- Check the initiator of every step. If a request was initiated by application code, it is at least one hop deeper than it needs to be, and a hint or a server-rendered reference may flatten it.
- Look for repeated setup segments. Multiple rows each paying DNS, connect and TLS means multiple origins, which is a structural cost you can often consolidate away.
- Throttle deliberately, and throttle latency rather than only bandwidth. Serial dependencies are invisible at low latency and dominate at high latency, so an unthrottled reading systematically hides the class of bug you are hunting.
- Compare cold and warm. Load once with an empty cache and once without; the difference tells you which of these bars your returning users never see (Browser HTTP Caching).
Keyboard, focus, semantics, announcement
A required field on every lesson in this domain, not a section added when there is room.
- The waterfall is the evidence for a set of accessibility problems, not just performance ones: a long stretch where nothing has arrived is a stretch where assistive technology has nothing to navigate.
- Late-arriving resources that change layout appear in the waterfall as bars that finish after first paint. Each one is a candidate for content moving under someone's pointer, switch target or magnified viewport (Visual Stability).
- A staircase ending in the request that fetches the page's actual text means a screen-reader user reaches an empty document and has to know to come back. Server-rendering the first screen removes that entirely (Server-Side Rendering).
- When you fix a waterfall by deferring work, check what a keyboard user can reach in the interval. A page that paints early and swallows keystrokes until hydration is a regression they feel and your waterfall does not show (Hydration).
What can go wrong
- Reading a waterfall recorded on a fast machine on office wifi and concluding the page is fine. This is the default failure and it is nearly universal.
- Mistaking queueing for slowness. The bar is long, the server is innocent, and the fix is upstream in what the browser was doing instead.
- Optimising a bar that is not on the critical path — measurable improvement in the total, no improvement in anything a user notices.
- Recording with an extension injecting requests, so the waterfall you are reading is not the waterfall your users get. Record in a clean profile.
- Flattening a staircase by requesting everything at once, and creating contention: the same bytes now compete, and the resource that mattered arrives later than before (Resource Hints).
- Preload-scanner requests and parser-initiated requests race, so document order does not predict request order.
- Two requests to the same origin race for the connection; on HTTP/1.1 one loses outright, and on HTTP/2 they interleave and both finish later than either would alone.
- A response and the script that consumes it race: a resource can be fully downloaded and sit unused because the main thread has not reached the code that needs it.
- The waterfall is the fastest inventory of who your page talks to. Every distinct origin is a party that can see the request, including the referrer and any cookies scoped to it (Third-Party Scripts and the Supply Chain).
- Requests that appear only sometimes — injected by an extension, a tag manager, or a script that was itself loaded by another script — are the supply-chain surface. A page whose request list is not deterministic cannot be reasoned about.
- A blocked request shows here with a distinct failure rather than a status code, which is how a Content-Security-Policy violation usually surfaces to a frontend engineer (Content Security Policy).
- Recording and sharing a waterfall exports URLs, and URLs routinely carry identifiers and tokens. Treat an exported trace as data with a privacy classification (Session Replay and the Privacy It Costs).
- "The longest bar is the problem." The longest bar is often parallel to everything that matters.
- "Everything is fast, so loading is fine." A waterfall with short bars arranged in a staircase is a slow page made of fast requests.
- "Total load time is the number."
loadfires long after the user could see and use the page, and long before a single-page application has finished fetching its data. - "Waiting for the first byte is the server's fault." It includes the network path in both directions; a nearby edge and a distant origin produce very different waits for identical server work (CDN Delivery).
Measuring it, and what changes in the field
- The Network panel: the waterfall itself, the initiator column, the priority column, and the timing breakdown for one request (Debugging the Network).
- The Performance panel over the top, so you can see which bars were competing with main-thread work rather than with each other.
- Field data for the shape, since the shape is what you are trying to generalise. A single local recording is one sample from a distribution you have not looked at (Real User Monitoring).
- A synthetic run with latency throttling, repeated. Waterfalls are noisy; one recording of a staircase is a hypothesis and three are a finding.
- On a high-latency link every step of a staircase costs a full round trip, so a four-step chain is four times the penalty of a one-step chain regardless of bytes.
- On HTTP/1.1 the per-origin connection limit produces a visible ladder of requests waiting their turn; on HTTP/2 and HTTP/3 that ladder disappears and is replaced by priority effects (HTTP/3 and QUIC in Networking).
- On a slow device the network can finish early and the main thread becomes the queue — bars end, nothing appears, and the waterfall is no longer where the answer is.
- On a repeat visit most rows vanish into cache hits, so a waterfall recorded without clearing the cache describes a user population you may not have.
- Reading waterfalls well is a skill with a real learning cost, and it produces qualitative findings that are hard to put in a dashboard.
- Throttling makes the staircase legible and makes every absolute number meaningless. You are trading measurement for structure, deliberately.
- Flattening dependencies usually means telling the browser about resources earlier, which means encoding assumptions in markup or headers that must be maintained as the page changes.
Where this applies
Frontend advice ages badly and fragments across engines. These labels say what each claim is specific to, and where a different browser, device or framework would differ.
- BROWSER-SPECIFICThe segment names differ: Chromium's timing breakdown labels a stall separately from queueing, Firefox groups them differently, and Safari's Web Inspector uses another vocabulary again. The underlying phases — queue, resolve, connect, secure, send, wait, receive — are the same everywhere; only the labels and the granularity move.
- NETWORK-SPECIFICThe classic six-connections-per-origin ladder is an HTTP/1.1 artefact. On HTTP/2 and HTTP/3 those rows start together and the interesting pattern becomes priority inversion instead, so a waterfall shape that indicates a real bug on one protocol is normal on another.
- SIMULATEDThe timeline in this lesson is a schematic drawn to make a staircase visible, not a recording. Real spans depend on the network, the device and the server; only the ordering and the dependency structure are the teaching.
Where the depth lives
This domain teaches the browser-side mechanism and hands the rest off.
- — Testing & Reliability Engineering — a waterfall shape is a testable assertion: "no request on the critical path is initiated by application code" can be enforced in CI long before anyone opens a devtools panel.