See your backend in the browser — the opaque Waiting bar, itemised.
The browser's network panel already splits a request into DNS lookup, connection, request sent, Waiting (TTFB), and content download. But everything the server did to produce the response — queries, cache lookups, business logic — is collapsed inside that single Waiting / TTFB bar. The Server-Timing response header lets the server itemise that opaque block, so the first byte you wait for becomes a readable breakdown of where the time actually went.
Comma-separated phase metrics, each name;dur=milliseconds, with an optional desc="…" label for humans. DevTools renders them inside the request's timing view, and JavaScript can read them through PerformanceServerTiming on Resource/Navigation Timing entries.
Values are chosen by the server, freely. They may overlap, may run in parallel, and never include network transit or un-instrumented queueing. The header explains the waiting bar — it does not partition it.
db 45.1 + cache 2.3 + app 15.7 = 63.1 ms. That sum is not required to equal TTFB. The rest is serialization, queueing, and anything the code never measured.
Cross-origin responses expose the header to JavaScript only when Timing-Allow-Origin permits it — though DevTools still displays the values.
Phase names and descriptions leak internal architecture (query engines, cache tiers, service names). Production deployments gate detail behind an internal-only condition.
Server-Timing: db;dur=45.1, cache;dur=2.3, app;dur=15.7 to the response. Only the server decides what to emit — an observability contract, not a measurement of truth; a slow phase can simply be left out.PerformanceServerTiming hands the server phases to JavaScript, so client phases (DNS, TTFB, download) and server phases (db, cache, app) travel together into your RUM data.Instrument an endpoint whose emitted phases sum to far less than its measured TTFB. Then add a deliberate, un-instrumented sleep before the handler. DevTools' TTFB grows while every Server-Timing value stays constant — proving the header is reported, not derived. Then remove Timing-Allow-Origin on a cross-origin fetch: PerformanceServerTiming returns empty while the network panel still displays the values.
PerformanceServerTiming returns empty in JavaScript. What is missing — and why does the panel still show the values?
One header gates the JS API, not the panel.