TTFB optimisation
Time to First Byte is five separate costs in one number. How to decompose it with Server-Timing, and what to do about each part.
TTFB is not a metric so much as a sum: DNS, TCP, TLS, server processing and the wait for the first byte to travel back. Teams routinely optimise the last of these when the first three account for most of the number — particularly for users in India hitting an origin in another region.
Decompose before optimising
| Component | Typical cost, India → nearby region | Lever |
|---|---|---|
| DNS | 20–120 ms on a cold lookup | A resolver with local presence; sensible TTLs; preconnect for third parties |
| TCP handshake | ~1 round trip | Keep-alive, HTTP/2 or HTTP/3 connection reuse |
| TLS | 1–2 round trips | TLS 1.3, session resumption, OCSP stapling |
| Server processing | 10 ms – 3 s | The part you control most and measure least |
| First byte transit | ~1 round trip | Serve from an edge close to the user |
const t0 = performance.now();
const rows = await db.query(sql);
const t1 = performance.now();
const html = render(rows);
headers.set('Server-Timing', [
`db;dur=${(t1 - t0).toFixed(1)}`,
`render;dur=${(performance.now() - t1).toFixed(1)}`,
`cache;desc="${hit ? 'HIT' : 'MISS'}"`,
].join(', '));The cold start nobody budgets for
Serverless and container platforms scale to zero. The first request after an idle period pays for a runtime boot, a module graph and often a fresh database connection — frequently one to three seconds, and it lands on a real user. On a low-traffic institutional system, a large share of visitors get a cold start, which is exactly the wrong population to give a slow first impression.
- Keep an instance warm during business hours where the platform allows it. On a Mon–Sat 10–19 workload that is a schedule, not a permanent cost.
- Cut the module graph on the request path. A dependency imported at module scope for one rarely-used route is paid for on every cold start.
- Put the connection pool behind a pooler so a new instance does not open a fresh set of database connections while a user waits.
Serve what you can from the edge
The fastest server response is one that never reaches your server. Static and revalidated pages served from a CDN node in-country turn a multi-hop origin request into a single short one — and for marketing and documentation pages there is no reason for anything else.
| Content | Strategy | Effect on TTFB |
|---|---|---|
| Marketing, docs, case studies | Static, cached at the edge | Origin removed from the path entirely |
| Listing pages that change hourly | Incremental revalidation, stale-while-revalidate | Users always hit cache; one unlucky request refreshes it in the background |
| Personalised dashboards | Dynamic, but with an edge-cached shell | The shell paints while the personalised part streams |
| Authenticated APIs | Dynamic, origin close to the database | Optimise processing, not delivery — an edge hop away from the data is slower |
Streaming changes what TTFB means
With streaming server rendering the first byte can leave before the slow data is ready: the shell goes out immediately and the slow region arrives when it resolves. TTFB improves genuinely, because the browser can start fetching stylesheets and scripts sooner — but only if the shell contains the resource references. A shell that streams nothing but a spinner has improved a number and not the experience.