TTFB request duration: verlaag de verwerkingstijd van de server

Het serverdeel van de TTFB: hoe DevTools en web-vitals het noemen, hoe Server-Timing de trage stap laat zien en waarom een page cache op één staat.

  • Goed ≤ 0,8 s
  • Verbetering nodig ≤ 1,8 s
  • Slecht > 1,8 s
Arjen Karel
Bijgewerkt · 10 min lezen
Delen

Request duration in het kort

Request duration is het laatste van de vijf onderdelen van Time to First Byte (TTFB): vanaf het versturen van het request voor de HTML tot de eerste byte binnenkomt. Het is het opbouwen van de pagina door de server plus één roundtrip over het netwerk. Op de meeste sites is dit het grootste deel van de TTFB: verlaag dit eerst, te beginnen met een page cache.

Wat is request duration, en is het "Waiting (TTFB)" in DevTools?

Request duration is wat Chrome DevTools in het tabblad Timing van het Network-paneel toont als "Waiting for server response", in oudere versies "Waiting (TTFB)": één roundtrip aan latency plus de tijd die de server nodig had om de response voor te bereiden. De web-vitals library rapporteert ditzelfde deel als requestDuration, CoreDash als Request in zijn Phase breakdown, en Lighthouse als server response time.

Het begint zodra de verbinding met de server klaar is en eindigt als de eerste byte van de HTML de browser bereikt. Daaraan vooraf gaan de andere vier onderdelen van de Time to First Byte: waiting, cache, DNS en connection. Dat waiting-deel bestaat vooral uit redirects, vóór het request; het is niet de 'Waiting' uit DevTools.

Het request was op het 75e percentiel het grootste deel van de TTFB bij 82 van de 86 CoreDash-sites met minstens 1.000 page loads op telefoons in de 28 dagen tot 9 november 2026.

Wat doet de server terwijl de browser wacht?

Het request reist naar de server en de eerste byte reist terug; daartussen bouwt de server de pagina op:

  1. Routing: de webserver, zoals nginx of Apache, geeft het request door aan de applicatie.
  2. Opstarten: de applicatie laadt haar code; WordPress laadt zijn core, elke actieve plugin en het theme voor elke pagina die het opbouwt.
  3. Data: de applicatie bevraagt de database en roept andere services aan, zoals een voorraad- of prijs-API.
  4. HTML: het template zet die data om in de pagina.
Timeline of one real first visit to a WordPress page, from the click to the first byte of the HTML, 875 milliseconds in total, split into the five parts of TTFB one after the other: waiting 174 ms (a redirect), cache 1 ms, DNS 23 ms, connection 20 ms and request 657 ms.

Eén eerste bezoek aan een WordPress-pagina die niet in de cache zat, gemeten in Chrome. Van de 875 ms TTFB kostte het request 657 ms.

Bij die load kostte de verbinding 20 ms voor twee roundtrips (TCP en TLS), dus één roundtrip was ongeveer 10 ms. Ongeveer 645 van de 657 ms ging op aan het opbouwen van de pagina door de server. Dicht bij de server is request duration vrijwel alleen servertijd; een bezoeker ver weg wacht daar bovenop nog op de roundtrip.

Er kan niets renderen voordat de eerste byte van de HTML binnenkomt, dus telt request duration ook op bij de Largest Contentful Paint.

Welke stap op de server is traag? Server-Timing laat het zien

Een Server-Timing header stuurt de timings van de server zelf mee met de HTML, met één entry per stap: de database, het template, een externe API, een cache hit of miss. Chrome DevTools toont de entries in het tabblad Timing van het HTML-request, onder "Waiting for server response", en een real-user monitoring (RUM) tool verzamelt ze van je bezoekers.

Server-Timing: db;dur=53.2;desc="Database", tpl;dur=18.7;desc="Template", cache;desc="MISS"
Chrome DevTools, Network panel, Timing tab of the HTML request on a local WordPress test install: Waiting for server response 58.98 ms, and under Server Timing wp (WordPress and query) 27.80 ms and tpl (Template) 27.10 ms.

Eén load op een lokale WordPress-testinstallatie, trager met DevTools open: de twee Server-Timing entries, 27,8 en 27,1 ms, passen binnen de 59 ms die de browser op de server wachtte.

Iedereen die DevTools opent kan de header lezen. Stuur alleen namen van stappen en tijdsduren mee, nooit een query, bestandspad of interne hostnaam, of stuur de header alleen naar ingelogde medewerkers (MDN).

Server-Timing in PHP

Meet elke stap met hrtime(true) en stuur de header vóór de pagina:

$t = hrtime(true);
$products = $pdo->query('SELECT * FROM products WHERE active = 1')->fetchAll();
$db = (hrtime(true) - $t) / 1e6;

$t = hrtime(true);
$html = renderTemplate('product-list', ['products' => $products]);
$tpl = (hrtime(true) - $t) / 1e6;

header(sprintf('Server-Timing: db;dur=%.1f;desc="Database", tpl;dur=%.1f;desc="Template"', $db, $tpl));
echo $html;

PHP stuurt zijn headers mee met de eerste output die de server verlaat. Een header()-aanroep daarna wordt genegeerd met een "headers already sent"-waarschuwing, dus heeft een template dat gaandeweg print output buffering nodig.

Server-Timing in Node.js en Express

In Express stel je de header in vóór res.send():

app.get('/products', async (req, res) => {
  const t0 = performance.now();
  const products = await db.query('SELECT * FROM products WHERE active = 1');
  const t1 = performance.now();
  const html = renderTemplate('product-list', { products });
  const t2 = performance.now();

  res.set('Server-Timing',
    `db;dur=${(t1 - t0).toFixed(1)};desc="Database", tpl;dur=${(t2 - t1).toFixed(1)};desc="Template"`);
  res.send(html);
});

Node verstuurt de headers bij de eerste res.write(). Stel in een route die streamt Server-Timing in vóór de eerste write: later gooit Node ERR_HTTP_HEADERS_SENT.

Hoe meet je request duration?

Request duration voor je bezoekers haal je uit real-user monitoring (RUM): CoreDash toont Request in de Phase breakdown van zijn TTFB-pagina, per pagina, en de web-vitals library rapporteert requestDuration in zijn TTFB attribution.

Google heeft geen drempelwaarde voor request duration; de Document request latency-melding van Lighthouse markeert een server response boven 600 ms. Over alle page loads op telefoons bij de 133 CoreDash-sites met telefoondata in de 28 dagen tot 9 november 2026 was het request-onderdeel 404 ms op het 75e percentiel.

In je eigen code geeft de web-vitals library ook de Server-Timing entries mee waarmee de pagina binnenkwam:

import { onTTFB } from 'web-vitals/attribution';

onTTFB(({ value, attribution }) => {
  console.log('TTFB', Math.round(value), 'ms');
  console.log('Request duration', Math.round(attribution.requestDuration), 'ms');
  const nav = attribution.navigationEntry;
  if (nav) {
    if (nav.finalResponseHeadersStart) { // Chrome 133 and later
      console.log('Final response headers at', Math.round(nav.finalResponseHeadersStart), 'ms');
    }
    console.table((nav.serverTiming || []).map(({ name, duration }) => ({ name, duration })));
  }
});

Beide getallen komen uit de Navigation Timing API: TTFB is de responseStart van de navigation entry, en web-vitals berekent requestDuration als TTFB minus connectEnd. De serverTiming-lijst van de entry bevat de entries uit de header als PerformanceServerTiming-objecten, de Server-Timing API in de browser, zodat een RUM-script ze bij elke pageview kan meesturen.

CoreDash Phase breakdown of Time To First Byte on phones for acme-pagespeed.com: Waiting + Redirect 41 ms, Worker & Cache 4 ms, DNS 6 ms and Connection 88 ms all good, Request 742 ms rated Improve; the page leaves Good during Request.

CoreDash, TTFB op telefoons opgesplitst in de vijf onderdelen op acme-pagespeed.com: zoek naar de langste balk, hier Request.

103 Early Hints laten de request duration kort lijken. Chrome telt een 103 als de eerste byte, waardoor TTFB en request duration al eindigen zodra de hint binnenkomt, terwijl de server de pagina nog opbouwt. Shopify stuurt ze, net als Cloudflare met Early Hints ingeschakeld. De finalResponseHeadersStart van de navigation entry of een Server-Timing entry laat de echte servertijd zien; 103 Early Hints legt de setup uit.

Een APM, of http.server.request.duration van OpenTelemetry, meet binnenin de applicatie, zonder het netwerk en een eventuele wachtrij ervoor; RUM toont daarom een hogere request duration. Eén load in DevTools ("Waiting for server response") is één cache hit of miss vanaf je eigen computer; field data bevat elke miss en elke bezoeker van ver weg. Hoe check en fix je een trage TTFB laat zien hoe je de pagina's en bezoekers vindt met een traag request-onderdeel.

Waarom is de server traag met het versturen van de eerste byte?

Op de meeste sites betekent een trage TTFB dat de server de pagina voor elke bezoeker opnieuw opbouwt: er is geen page cache, of de bezoeker omzeilt die. Pagina's die niet gecachet kunnen worden zijn traag door het werk dat ze doen: een trage query, een externe API waarop de pagina wacht, te weinig CPU.

Wat je ziet Gebruikelijke oorzaak Waar je kijkt
Traag op elke pagina, uitgelogd Geen page cache, of een miss De cache-header: HIT of MISS
Traag ingelogd of met een winkelmand De pagina kan niet gecachet worden Server-Timing
Traag via links uit advertenties en mail Een querystring die niet in de cache zit De URL met en zonder ?gclid=
Traag op zoek- en accountpagina's Een trage query of een externe call Server-Timing, Query Monitor
Dichtbij snel, ver weg traag De roundtrip naar je server RUM per land
Vanaf een bepaalde dag trager Een release, of een kapotte cache De trend van het request-onderdeel in RUM
Traag na een rustig uur Een verlopen cache of een cold start De eerste load vergeleken met de tweede

Hoe verlaag je request duration?

Begin met een page cache. Een opgeslagen kopie van de pagina slaat het werk van de server over, en op de meeste sites is dat werk het grootste onderdeel van een trage time to first byte (TTFB). Kunnen pagina's niet gecachet worden, dan moet de server zelf sneller worden.

WordPress en WooCommerce

Zet de page cache aan in WP Rocket, LiteSpeed Cache of bij je host. Mijn plugin, WP Core Web Vitals, stuurt de gecachete pagina al vóór WordPress en de andere plugins laden, zo'n vijf keer sneller dan dezelfde pagina uitserveren vanuit WordPress zelf.

Controleer het resultaat uitgelogd, in een privévenster: ingelogde gebruikers, een winkelmandcookie en een querystring die de cache niet kent, omzeilen de cache.

Op een lokale WordPress-testinstallatie met een standaard theme verlaagde de page cache het request-deel van de homepage van 31 ms naar ongeveer 1 ms, de mediaan van 15 loads elk. Een echte site doet er langer over om een pagina op te bouwen; de gecachete kopie komt ongeveer even snel terug.

Bar chart of the request duration on a local WordPress test install, median of 15 loads each: 31.3 ms with the page cache off and 1.1 ms with the page cache on.

Een lokale installatie heeft geen netwerk, dus beide balken zijn het werk van de server.

Een membership-site waar iedereen is ingelogd, of een webshop waar de meeste bezoekers een winkelmand hebben, heeft weinig aan een page cache: elk request vereist daar PHP- en databasewerk. Een object cache zoals Redis bewaart herhaalde databaseresultaten in het geheugen; bij veel hosts is dat een schakelaar of een vraag aan support. Query Monitor toont de trage database-queries en de plugin waar ze vandaan komen.

Shopify

De server, de cache en het CDN zijn van Shopify. Jouw deel is de Liquid van het theme en de app blocks, die Shopify op zijn servers rendert. Shopify Theme Inspector voor Chrome toont welke sectie of snippet lang duurt: snoei die bij, of vervang de app die hem toevoegt.

Een page cache vóór je applicatie

Een reverse proxy, het CDN of een cache die antwoordt vóór je framework opstart, slaat het werk van de applicatie over. Deze header laat een CDN of proxy de HTML tien minuten bewaren en nog één minuut serveren terwijl er een verse kopie wordt opgehaald, en zorgt dat de browser bij elk bezoek opnieuw checkt:

Cache-Control: public, max-age=0, s-maxage=600, stale-while-revalidate=60

Ik heb mijn WordPress-plugin het gecachete bestand niet rechtstreeks vanuit nginx of Apache laten serveren: dat zou nauwelijks sneller zijn, en elke check op cookies en URL-parameters zou je moeten overnemen in de serverconfiguratie.

Een Set-Cookie op elke response, voor een sessie of een A/B-test, houdt de pagina vaak uit de cache van een CDN. Vary: Cookie vraagt om een aparte kopie per cookiewaarde, wat bij analytics-cookies leidt tot vrijwel nul cache hits. Elke nieuwe gclid- of utm_source-waarde uit een advertentie levert een nieuwe URL op, tenzij de cache die parameters negeert. Wil je de server meten zonder de cache, laad de pagina dan met een parameter die je caches niet negeren, zoals ?test= met telkens een nieuwe waarde, en controleer of elke cache-header op MISS staat.

Een CDN dat de HTML bewaart

Een CDN dat de HTML cachet antwoordt vanaf een server dicht bij de bezoeker. Daardoor bevat de time to first byte niet langer de reis naar je server of het werk ervan. Een CDN dat alleen afbeeldingen en scripts cachet, stuurt elk HTML-request door naar je eigen server. Op Cloudflare wordt HTML alleen gecachet via een Cache Rule, of APO voor WordPress: Configureer Cloudflare voor de Core Web Vitals toont de instellingen. Code op de edge van het CDN die je database nodig heeft, moet alsnog wachten op die roundtrip.

Pagina's die per bezoeker worden opgebouwd

Ingelogde pagina's, de winkelmand en zoekresultaten kunnen niet uit een page cache komen; Server-Timing wijst de trage stap aan. Voor de database: een index voor de trage query, en één query voor een lijst in plaats van één query per item (het N+1-patroon). Een pagina die wacht op een database of API in een ander datacenter voegt die roundtrip toe aan elk request: cache het antwoord, en roep services die niet van elkaar afhankelijk zijn tegelijk aan.

const [stock, prices] = await Promise.all([getStock(id), getPrices(id)]);

E-mail versturen, logs schrijven en voorraad synchroniseren kan wachten: doe dat na de response of in een queue.

De server en de host

Blijkt uit Server-Timing en je RUM-data dat de server zelf traag is, dan is een nieuwe host zinvol: de gecachete pagina's zijn snel, en bij de pagina's zonder cache duurt elke stap lang. Veel hosting-upgrades voegen alleen opslag toe; een snellere server heeft juist meer CPU en geheugen nodig. Draai een actuele PHP-versie met OPcache aan. Een serverless function wacht na een rustige periode op een cold start: houd een instance warm, of zet er een page cache voor.

JavaScript-frameworks (Next.js, Nuxt, SvelteKit)

Een JavaScript-framework dat elke pagina per request op de server rendert, geeft de TTFB van een niet-gecachete pagina. In de praktijk wordt CMS-content voor elke pagina live opgehaald en staat Cache-Control: private of no-store aan, waardoor het CDN de HTML ook niet kan bewaren. Render pagina's statisch of gecachet waar de content dat toelaat (in Next.js: use cache), cache de CMS-fetches en laat het CDN de HTML bewaren. Stream wat dynamisch blijft achter <Suspense>, zodat de head met de stylesheets als eerste verstuurd wordt. Lees je cookies of headers uit in een layout, dan rendert elke onderliggende pagina per request.

Is de server sneller geworden voor echte bezoekers?

Check eerst één load, uitgelogd in een privévenster, en daarna eentje met ?gclid= en een nieuwe waarde. De response headers moeten een cache hit tonen (cf-cache-status: HIT op Cloudflare, X-CWV-Cache: HIT bij WP Core Web Vitals) en je Server-Timing entries; een MISS bij de ?gclid=-load betekent dat bezoekers via advertenties op de server wachten. Bij een release moet de HTML ook op het CDN gewist worden, anders blijven bezoekers de oude pagina krijgen.

De volgende dag toont het Request-onderdeel in je RUM-data de verandering. PageSpeed Insights toont alleen de volledige TTFB over de afgelopen 28 dagen, dus die beweegt langzaam.

Sommige trage requests vragen om meer dan een cache of een instelling: een webshop waar elke bezoeker een winkelmand heeft, of een host of backend waar je niets aan kunt veranderen. In één Core Web Vitals power hour nemen we je Server-Timing entries en traagste pagina's door, en bepalen we waar de server als eerste mee stopt en wie dat doet: je host of je developer.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Arjen Karel

Een snellere site nodig?

Ik ben Arjen en ik maak al 17 jaar trage sites snel. Loop je tegen iets aan dat je zelf niet opgelost krijgt? Stuur me je URL, dan praten we even.

Stuur me je URL
Arjen Karel
Arjen Karel, Web performance consultant sinds 2009

Waarom werkt onze fix niet?

Soms serveert het CDN nog de oude HTML, of de fix zit in het desktop-template terwijl mobiel een ander template gebruikt. Stuur me je URL en het probleem, dan stuur ik je een paar momenten. Ik bekijk eerst je field data, daarna besteden we een uur aan je site, en binnen een dag heb je mijn notities op schrift. €350 excl. btw.

Stuur me je URL

Geen developer om de fix te bouwen? Ik doe het werk zelf, op een kopie van je site.

Arjen KarelTTFB nog steeds rood op mobiel?Stuur me je URL