Skip to content
HandbookPublic
Security

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.

Written forEngineers who own both the application and its delivery path
Reading time10 min read
Last reviewed2026-09-04

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

ComponentTypical cost, India → nearby regionLever
DNS20–120 ms on a cold lookupA resolver with local presence; sensible TTLs; preconnect for third parties
TCP handshake~1 round tripKeep-alive, HTTP/2 or HTTP/3 connection reuse
TLS1–2 round tripsTLS 1.3, session resumption, OCSP stapling
Server processing10 ms – 3 sThe part you control most and measure least
First byte transit~1 round tripServe from an edge close to the user
ts
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(', '));
Server-Timing puts your own breakdown in the browser's network panel and in RUM. Cheap, and it ends the guessing.

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.

ContentStrategyEffect on TTFB
Marketing, docs, case studiesStatic, cached at the edgeOrigin removed from the path entirely
Listing pages that change hourlyIncremental revalidation, stale-while-revalidateUsers always hit cache; one unlucky request refreshes it in the background
Personalised dashboardsDynamic, but with an edge-cached shellThe shell paints while the personalised part streams
Authenticated APIsDynamic, origin close to the databaseOptimise 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.