Use efficient cache lifetimes in Lighthouse: zo los je het op
Lighthouse 13 toont elk bestand dat de browsers van je bezoekers korter dan 30 dagen kunnen hergebruiken. Wat dat kost bij de volgende pagina en het terugkerende bezoek, en de fix per server en platform.
Is "Use efficient cache lifetimes" slecht voor je Lighthouse-score?
Lighthouse markeert elk statisch bestand met een cache-levensduur korter dan 30 dagen in het insight Use efficient cache lifetimes ("A long cache lifetime can speed up repeat visits to your page"), vóór Lighthouse 13 "Serve static assets with an efficient cache policy" genoemd en in oudere PageSpeed Insights "Leverage browser caching". Het telt niet mee voor je score en kan die ook niet veranderen: Lighthouse laadt altijd met een lege cache. Stel een langere levensduur in op je server, CDN of caching-plugin.
Table of Contents!
Wat betekent Use efficient cache lifetimes in Lighthouse?
De omschrijving van Lighthouse luidt: "A long cache lifetime can speed up repeat visits to your page. Learn more about caching."
Lighthouse toont elk script, elke stylesheet, font, afbeelding, video en elk audiobestand met een cache-levensduur korter dan 30 dagen, of helemaal zonder levensduur. Die levensduur is de max-age in de Cache-Control-header van het bestand, of anders de tijd tot de Expires-datum. Bestanden van andere domeinen staan er net zo tussen als die van jezelf.
Bevat de Cache-Control van een bestand no-cache, no-store, private of must-revalidate, dan laat Lighthouse het weg, ongeacht de max-age: dat ziet het als een bewuste keuze om niet te cachen. Bestanden met max-age=0 laat het ook weg, en de HTML-pagina zelf staat er nooit tussen.

Een pagina die Google Tag Manager, GA4 en de Meta Pixel laadt. Google verstuurt zijn tags als private, waardoor van de drie alleen de Meta Pixel in de lijst staat. Lighthouse 13.5.0, gesimuleerde throttling zoals in PageSpeed Insights.
Hoe werkt browser caching?
De browser slaat een bestand op samen met de levensduur. Bij een volgende pagina of een volgend bezoek kijkt hij eerst naar de opgeslagen kopie:
- Binnen de levensduur: de browser gebruikt zijn eigen kopie en verstuurt helemaal geen request. Alleen in dit geval hoef je niet op de server te wachten.
- Levensduur verstreken: de browser vraagt met de meegestuurde ETag of Last-Modified-datum aan de server of het bestand gewijzigd is. Is dat niet zo, dan antwoordt de server met 304 Not Modified, zonder het bestand. Dat scheelt een download, maar elk bestand kost nog steeds een round trip naar de server, en de pagina kan pas renderen als de server voor elke stylesheet heeft geantwoord.
- Levensduur verstreken en geen ETag of Last-Modified: de browser downloadt het bestand opnieuw.
| Header op het bestand | Volgende weergave in Chrome |
|---|---|
max-age=31536000 | Een jaar lang uit de cache |
max-age=14400 | 4 uur lang uit de cache, daarna een 304 |
no-cache | Elke keer een 304 |
no-store | Elke keer opnieuw gedownload |
Bij een normale reload controleert Chrome bestanden die nog binnen hun levensduur vallen niet opnieuw. Firefox en Safari doen dat wel, tenzij het bestand immutable in de Cache-Control heeft staan.
Sinds Chrome 86 heeft elke site een eigen cache. Een font of jQuery-bestand dat een bezoeker al op een andere site heeft gedownload, wordt op jouw site opnieuw gedownload, zelfs vanaf exact dezelfde URL. De levensduur van een third-party helpt dus alleen op je eigen site, bij de volgende pagina en het terugkerende bezoek. "Iedereen heeft het toch al gecachet" is dan ook geen reden meer om fonts via Google te laden (host ze zelf).
De browsercache en de cache van je CDN zijn twee verschillende dingen, net als de page cache van een WordPress-cachingplugin. Een CDN of page cache bewaart een kopie zodat je server die niet voor elke bezoeker hoeft te bouwen of te versturen. Dat verkort de TTFB (cache duration). De browsercache bespaart het hele request, voor één bezoeker. Dit insight gaat alleen over de browsercache; een page cache die al aan staat, lost deze melding dus niet op.
Verbetert een langere cache-levensduur de LCP en FCP?
FCP en LCP bij een eerste bezoek
Geen verschil. Op een WooCommerce-productpagina, op een telefoon via Slow 4G, was de first paint bij elke header 2,13 s en kwam de LCP uit op 2,26 s, van geen enkele header tot een jaar: bij een eerste bezoek staat er immers nog niets in de cache. Lighthouse en PageSpeed Insights laden een pagina altijd alleen op deze manier. Los je dit insight op, dan veranderen je Lighthouse-score en metrics nooit, zelfs niet als Lighthouse het in het rood toont met FCP- en LCP-besparingen erbij.

Geen levensduur

De 4 uur van Cloudflare

Eén jaar

no-store
Dezelfde productpagina met vier verschillende cache-headers op de bestanden. Lighthouse 13.5.0, mobiel, gesimuleerde throttling.
FCP en LCP bij een volgende pagina en een terugkerend bezoek
De first paint (FCP) in seconden, vergeleken met 2,13 s bij het eerste bezoek:
| Cache-header | Volgende pagina | Een week later |
|---|---|---|
| een jaar | 0,79 | 0,78 |
| 4 uur | 0,80 | 1,94 |
no-cache | 1,93 | 1,94 |
no-store | 2,11 | 2,12 |
- De winst zit in bestanden binnen hun levensduur. Een week later had de pagina met een levensduur van een jaar zijn first paint al op 0,78 s, ongeveer een derde van het eerste bezoek. Het enige wat overblijft, is het wachten op de HTML. Bij het terugkerende bezoek viel de LCP, de productfoto, bij elke header op hetzelfde moment als de first paint, behalve bij no-store (2,24 s).
- Met 304's was het terugkerende bezoek bijna net zo traag als een eerste bezoek. Zonder levensduur kwamen een week later alle 13 eigen bestanden van de shop terug als 304: 1 kB over het netwerk in plaats van 404 kB, en alsnog een first paint op 1,94 s, slechts 0,19 s sneller dan alles opnieuw downloaden.
- Korte levensduren dekken het actieve bezoek en verlopen vóór de terugkeer. De 4 uur van Cloudflare serveerde de volgende pagina uit de cache. Een week later waren er 304's nodig. Alleen de instelling van een jaar dekte beide.
- no-cache en no-store vertragen elke pagina. Ze brachten de volgende pagina van 0,79 s naar 1,93 s en 2,11 s.

Geen levensduur, een week later

Een jaar levensduur, een week later
Beide pagina's op 0,8 s op de gethrottlede telefoon.
Op een echte Slow 4G-verbinding duurt elke weergave langer; de onderlinge volgorde van de headers blijft gelijk. Op Fast 4G had een levensduur van een jaar bij het terugkerende bezoek een first paint op 0,52 s, en duurde de terugkeer met 304's 0,85 s: net zo lang als het eerste bezoek op die verbinding.
Wanneer kun je Use efficient cache lifetimes negeren?
Toont Lighthouse je eigen bestanden en komen bezoekers dagen of weken later terug, los het dan op: het vraagt één regel op de server en kost niets tijdens het laden van de pagina. Laat het zitten als:
- elk vermeld bestand op een ander domein staat of door je platform wordt geserveerd (zie hieronder); op Shopify is dat altijd zo;
- je CSS en JavaScript onder dezelfde bestandsnaam wijzigen en je geen versie kunt toevoegen: een korte levensduur is dan de juiste keuze, en Lighthouse blijft die bestanden tonen;
- vrijwel elke bezoeker maar één pagina bekijkt en nooit meer terugkomt, zoals bij een landingspagina voor een advertentiecampagne: niemand gebruikt de cache.
Hoe meet je het effect van cache-levensduren in je field data?
In je field data lopen eerste bezoeken, volgende pagina's en terugkerende bezoeken door elkaar. Groepeer in CoreDash de LCP op mobiel op Repeat Visitor, en doe daarna hetzelfde voor TTFB:


CoreDash op een demosite, acme-pagespeed.com: mobiel, p75.
Terugkerende bezoekers hebben een LCP van 0,65 s, nieuwe bezoekers 1,74 s. Niet dat hele gat komt door de cache: terugkerende bezoekers ontvangen de eerste byte ook sneller, 323 ms tegenover 0,87 s, vaak omdat de browser al een open verbinding met je server heeft. Trek de TTFB daarom af van de LCP. Na de eerste byte wachten terugkerende bezoekers 0,33 s en nieuwe bezoekers 0,87 s. Die 0,54 s is grofweg wat de cache bespaart, ongeveer de helft van het verschil; de snellere eerste byte is de andere helft.
Wil je de volgende pagina van een bezoek los bekijken, groepeer dan op Navigation Origin: same origin betekent dat de bezoeker van een andere pagina op je site kwam. CrUX gooit al deze bezoeken op één hoop in één p75 per pagina of per site, en kan deze splitsing dus niet tonen.
Hoe controleer je de cache-headers van een bestand in DevTools?
Open het Network-paneel en zorg dat Disable cache uit staat (de DevTools-netwerkinstellingen leggen uit wanneer je dit aanzet). Laad je pagina en klik door naar een tweede pagina. De kolommen Status en Size laten zien wat er met elk bestand is gebeurd:
- (disk cache) of (memory cache) bij Size: uit de cache gehaald, geen request.
- 304 bij Status: de browser vroeg het aan de server en kreeg geen bestand terug.
- 200 met een bestandsgrootte: opnieuw gedownload.

Geen levensduur: 304 voor elk bestand

Eén jaar: vanuit de disk cache
Het terugkerende bezoek een week later in het Network-paneel, Slow 4G.
Klik op een bestand en open het tabblad Headers. Onder Response Headers tonen cache-control en expires de levensduur die je server meestuurde; etag en last-modified tonen waarmee de browser controleert op wijzigingen. Vanaf de command line:
curl -sI https://www.example.com/wp-content/themes/acme/style.css | grep -iE "cache-control|expires|etag|last-modified" Het Performance-paneel bevat hetzelfde insight. Neem een reload op en open Use efficient cache lifetimes in de Insights-sidebar: je ziet de bestanden in een lijst en rood omlijnd in de Network-track. Een bestand zonder levensduur staat daar als 0 ms, en in Lighthouse als None. Dit kijkt puur naar de headers; een bestand dat uit de cache kwam, staat er dus alsnog tussen als de levensduur kort is.

Hoe los je Use efficient cache lifetimes op?
Het domein van elk bestand dat Lighthouse toont, verraadt wie de header instelt: op je eigen domein is dat je server, je host of je CDN; op elk ander domein is het de leverancier van dat bestand.
nginx, Apache, IIS en Caddy
Op je eigen server stel je de levensduur in met één regel per bestandstype. Onderstaande regels stellen een jaar in, met immutable: gebruik ze voor afbeeldingen, en voor CSS, JavaScript en fonts zodra hun URL's een versie bevatten (welke levensduur voor welk bestand). In nginx:
location ~* \.(?:css|js|mjs|woff2|svg|png|jpe?g|gif|webp|avif|ico)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
} Plaats dit in het server-block dat deze bestanden van schijf serveert. Heeft je configuratie al een location voor deze extensies, voeg de add_header-regel dan daaraan toe; nginx gebruikt namelijk alleen de eerste matchende regex-location. Verwijder eventuele expires-regels daaruit, anders krijgen de bestanden twee Cache-Control-headers. Een add_header binnen een location zorgt dat nginx alle add_header-regels uit het server-block voor die bestanden negeert. Kopieer de add_header-regels van het server-block (zoals je HSTS-header) daarom ook naar deze location.
Zet dit voor Apache in .htaccess:
<IfModule mod_headers.c>
<FilesMatch "\.(css|js|mjs|woff2|svg|png|jpe?g|gif|webp|avif|ico)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule> FilesMatch kijkt naar de bestandsnaam, waardoor pagina's die PHP opbouwt hun eigen headers behouden. Toont Lighthouse de bestanden daarna nog steeds, dan past je server de regel niet toe: mod_headers staat uit, of je host serveert deze bestanden vanaf een andere server die .htaccess negeert. Vraag dit na bij je host.
IIS stuurt standaard helemaal geen levensduur mee. Stel de header in via web.config, beperkt tot de map met je statische bestanden:
<configuration>
<location path="assets">
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Cache-Control" value="public, max-age=31536000, immutable" />
</customHeaders>
</httpProtocol>
</system.webServer>
</location>
</configuration> In Caddy:
@static path *.css *.js *.mjs *.woff2 *.svg *.png *.jpg *.jpeg *.gif *.webp *.avif *.ico
header @static Cache-Control "public, max-age=31536000, immutable" WordPress
WordPress zelf stuurt geen levensduur mee voor bestanden in /wp-content/ of /wp-includes/: dat bepaalt je host, je server of een plugin. Kinsta, WP Engine, WordPress VIP en WordPress.com sturen standaard al een jaar mee: host je daar, dan staan de bestanden die Lighthouse toont meestal op andere domeinen (zie hieronder). De melding verschijnt vooral bij shared hosting op Apache die geen levensduur meestuurt, op een eigen nginx-server en achter de standaardinstelling van Cloudflare. Bij de caching-plugins:
- WP Rocket schrijft mod_expires-regels naar .htaccess op Apache- en LiteSpeed-servers: een jaar voor CSS en JavaScript, vier maanden voor afbeeldingen en fonts. Op nginx doet de plugin niets: voeg de bovenstaande nginx-regel toe of vraag het je host.
- LiteSpeed Cache heeft Browser Cache standaard uitgeschakeld staan. Zet dit aan onder LiteSpeed Cache, Cache, Browser; daarna stuurt het een jaar mee.
- W3 Total Cache heeft Browser Cache standaard ingeschakeld staan. Op nginx genereert de plugin een nginx.conf die je zelf moet toevoegen aan je serverconfiguratie.
WordPress plakt ?ver= achter elke stylesheet en elk script dat het laadt: standaard de WordPress-versie, of de versie die het thema of de plugin meegeeft. Dat verandert alleen bij een update. Pas je je eigen CSS aan zonder de versie te verhogen, dan blijft de URL gelijk en blijven terugkerende bezoekers de oude CSS houden zolang de levensduur loopt. Laad je eigen bestanden daarom in met hun wijzigingstijd als versie, zodat de URL bij elke bewerking verandert:
add_action( 'wp_enqueue_scripts', function () {
$file = '/css/custom.css';
wp_enqueue_style( 'acme-custom', get_stylesheet_directory_uri() . $file, array(), filemtime( get_stylesheet_directory() . $file ) );
} ); Uploads hebben geen versie. Upload je een bestand opnieuw, dan geeft WordPress het een nieuwe naam (photo-1.jpg). Een jaar is daarvoor dus veilig, tenzij een plugin media onder dezelfde naam overschrijft.
Cloudflare en andere CDN's
De Browser Cache TTL van Cloudflare staat op elk plan standaard op 4 uur. Stuurt je server geen of een kortere levensduur mee, dan maakt Cloudflare er 4 uur van en toont Lighthouse het bestand als "4h". Stel de levensduur in op je server en zet Browser Cache TTL (Caching, Configuration) op Respect Existing Headers. Of maak voor je statische bestandstypen een Cache Rule aan die de browser-TTL instelt. Verhoog de instelling voor de hele zone niet naar een jaar: die geldt dan voor elk bestand dat Cloudflare met een kortere header serveert, inclusief CSS zonder versie. Meer over de overige instellingen lees je in configure Cloudflare for the Core Web Vitals.
CloudFront en Fastly geven de header van je server direct door aan de browser; hun TTL-instellingen bepalen alleen hoe lang het CDN de eigen kopie bewaart. Bij Bunny stelt een Edge Rule met Override Browser Cache Time de levensduur per bestandsextensie in.
Shopify
Shopify geeft je themabestanden, uploads en productafbeeldingen standaard al een jaar mee (public, max-age=31557600), en die headers van Shopify kun je niet aanpassen. Wat Lighthouse op Shopify toont, is afkomstig van Shopify zelf en je apps: de dynamische checkout-knoppen (portable-wallets, 5 minuten, op je eigen domein onder /cdn/shopifycloud/), de shop-js loader (30 minuten) en de web pixels manager (60 seconden). Er valt niets te fixen. Negeer de melding.
Next.js en andere build-tools
Next.js geeft alles onder /_next/static/ een jaar mee met immutable; dat kun je niet overschrijven. Bestanden in public/ krijgen max-age=0 (op Vercel met must-revalidate) en /_next/image stuurt must-revalidate mee, waardoor het insight beide overslaat. Op een Next.js-site zijn de bestanden die Lighthouse toont afkomstig van third-parties, of van een proxy of CDN ervoor die de headers herschrijft. Voor bestanden in public/ waarvan de naam mee verandert met de inhoud, stel je in next.config.js een jaar in:
module.exports = {
async headers() {
return [{
source: '/fonts/:path*',
headers: [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }],
}]
},
} Laat bij elke andere stack de bundler een hash in de bestandsnamen zetten (app.3f9a1c.js), zodat elke wijziging een nieuwe URL krijgt. Geef die bestanden vervolgens een jaar mee.
Third-party- en platformbestanden die je niet kunt aanpassen
Een leverancier houdt de levensduur van zijn script kort om het op alle sites tegelijk te kunnen updaten, en die headers kun je niet veranderen. Lighthouse toont deze bestanden vaak, met de levensduur die de leverancier meegeeft:
- Meta Pixel (fbevents.js): 20 minuten
- Hotjar: 60 seconden
- Het oude analytics.js van Google Analytics: 2 uur
- Google Maps (maps/api/js): 30 minuten
- Stripe (js.stripe.com/v3): 2 minuten
- De eigen scripts van Cloudflare op je domein (email-decode.min.js, Rocket Loader): 2 dagen
Laat dit zo. Ga een loader zoals fbevents.js of gtag.js niet zelf hosten om er een eigen header op te krijgen: je kopie ontvangt dan geen updates van de leverancier meer. Wat je wel kunt doen:
- Laad een library in vanaf een publiek CDN met een exacte versie in de URL. jsDelivr geeft swiper@11 zeven dagen en een exacte versie een jaar. Of host die vastgezette versie zelf, onder je eigen header.
- Schakel functies van Cloudflare uit die je niet gebruikt, zoals Email Address Obfuscation.
- Verwijder tags die niemand meer gebruikt.
Welke cache-levensduur stel je in voor welk bestand
- CSS, JavaScript en fonts met een versie of hash in de URL: een jaar, met immutable:
Cache-Control: public, max-age=31536000, immutable. Een nieuwe versie betekent een nieuwe URL, dus de browser haalt die direct op. - Afbeeldingen: dezelfde header. Geef een gewijzigde afbeelding een nieuwe bestandsnaam, dan krijgt niemand ooit de oude te zien.
- CSS, JavaScript en fonts zonder versie: voeg die eerst toe (zie de stappen voor WordPress en build-tools hierboven). Zolang dat niet kan, houd je de levensduur kort en accepteer je dat Lighthouse ze toont: bij een lange levensduur op een stylesheet die onder dezelfde naam wijzigt, krijgen terugkerende bezoekers je nieuwe HTML met de oude CSS te zien.
- De HTML:
Cache-Control: no-cacheof een korte max-age, want met een lange levensduur blijven bezoekers op een verouderde pagina hangen. - Gebruik nooit no-store, no-cache of must-revalidate op statische bestanden om van de melding af te zijn: elke paginaweergave controleert of downloadt elk bestand dan opnieuw (zie de volgende pagina hierboven).
Hoe controleer je of je nieuwe cache-levensduren werken?
Controleer eerst de header op het bestand met curl of via het Headers-tabblad (hierboven): daar hoort de nieuwe max-age te staan. Draai daarna Lighthouse op mobiel. De bestanden staan niet meer in de lijst en de score en metrics blijven gelijk, zoals in de vier rapporten hierboven. Op de productpagina kwam de score van run tot run uit op 96 of 80, en welke het werd had niets met de header te maken. Een andere score na de fix zegt je dus niets over de cache.
In CoreDash zie je het bij terugkerende bezoekers al binnen een paar dagen terug, zodra ze terugkomen en de bestanden met de nieuwe levensduur ophalen. CrUX en Search Console tonen de afgelopen 28 dagen: twee weken na de fix bestaat de helft van die data dus nog uit de oude site.
Weet je niet zeker welke bestanden uit de lijst van jou zijn om te fixen, of vraag je je af of terugkerende bezoekers wachten op bestanden die ze al hebben? In een Core Web Vitals power hour lopen we samen door je headers en field data heen, en krijg je de regels voor je server voor we de call afronden.
Ik lever code, geen rapport.
Ik schuif 1 of 2 sprints aan bij je team. Ik zet de monitoring op zodat je metrics groen blijven als ik weg ben.
Bel me