Logboek / Ontwikkeling van webproducten
Hoe de cache werkt in Next.js v13+ met de App Router

Een gedetailleerd overzicht van hoe caching werkt in Next.js 13+, welke soorten cache er bestaan en hoe dat prestaties en SEO beïnvloedt.
Wat is cache en hoe werkt het in Next.js
Een cache slaat de resultaten van berekeningen of requests op, zodat ze niet opnieuw hoeven te worden uitgevoerd, wat de applicatie sneller maakt. In Next.js kunnen HTML-pagina's, data, requests, het renderen van componenten en zelfs hele routes in de cache worden bewaard.
Met de komst van de App Router (vanaf Next.js 13) is de filosofie veranderd: caching staat nu standaard aan, en het is aan de developer om het uit te schakelen wanneer dynamisch gedrag nodig is.
Het beleid van Next.js is als volgt:
"We cachen alles, tenzij anders aangegeven"
Als je niet expliciet cache: 'no-store', next: { revalidate } of dynamic = 'force-dynamic' opgeeft, dan:
- wordt
fetch()gecachet - kunnen layout en pagina ook gecachet worden
- worden routes toegevoegd aan de routecache
- gaat het renderen van de pagina over naar de volledige routecache
Dat zorgt voor snelheid, maar kan leiden tot verouderde data en lastig te debuggen problemen.
Soorten cache in Next.js

Bron: Next.js-documentatie
Volledige routecache (routecache aan de serverzijde)
Wordt gebruikt met ISR (Incremental Static Regeneration).
- De hele pagina wordt gecachet (HTML, data uit
layout.tsx,page.tsx,fetch(),headers(),cookies()). - De cache staat op de server.
- Werkt standaard wanneer er geen dynamisch gedrag is.
- Wordt uitgeschakeld met
revalidateofrevalidateTag().
Het beste voor blogs, marketingpagina's en andere content die zelden verandert.
Voorbeeld:
export const revalidate = 60; // regenerate the page every 60 seconds
Datacache (cache voor het ophalen van data aan de serverzijde)
- Antwoorden van
fetch()in Servercomponenten worden gecachet. - Te sturen met
next: { revalidate }ofcache: 'no-store'.
Voorbeeld:
await fetch('https://api.com/posts', {
next: { revalidate: 300 },
});
Request-memoization (cache per request, aan de serverzijde)
Dit is een interne optimalisatie van Next.js: dezelfde fetch() of functie met dezelfde argumenten wordt per render maar één keer aangeroepen.
- Binnen één serverrender houdt Next.js identieke requests bij.
- Zijn ze al aangeroepen, dan komt het resultaat uit de cache.
- Werkt onafhankelijk van
cache: 'force-cache'ofno-store.
Goed voor:
- DRY (don't repeat yourself) bij het ophalen van dezelfde data in verschillende componenten.
- Betere prestaties zonder handmatige optimalisatie.
- Wanneer dezelfde
fetchzowel inlayout.tsxals inpage.tsxwordt gebruikt.
Niet voor:
- Hergebruik tussen verschillende requests of gebruikers (het is geen globale cache).
- Het omzeilen van de datacache: werkt op basis van request-memoization.
// lib/getUser.ts
export async function getUser() {
return fetch('https://api.com/user').then(res => res.json());
}
// app/layout.tsx
export default async function Layout({ children }) {
const user = await getUser(); // Call 1
return (
<div>
<Sidebar user={user} />
{children}
</div>
);
}
// app/page.tsx
import { getUser } from '../lib/getUser';
export default async function Page() {
const user = await getUser(); // Call 2, but actually not repeated
return <p>Hello, {user.name}</p>;
}
Fysiek wordt fetch maar één keer aangeroepen en het resultaat wordt op beide plekken gebruikt.
Routercache (cache aan de clientzijde)
Dit is de cache die je zelf beheert met bibliotheken als SWR, React Query en andere.
- Werkt alleen bij navigatie aan de clientzijde (
<Link>,router.push()). - Next.js beheert deze cache niet: jij bepaalt hoe je haar onthoudt.
- Heel snel, omdat er niets opnieuw wordt gerenderd: React mount gewoon vanuit het geheugen.
- Maar ze bestaat alleen totdat de gebruiker de pagina herlaadt (F5).
Het beste voor interactieve componenten, formulieren en dashboards.
Voorbeeld:
'use client';
import useSWR from 'swr';
const { data } = useSWR('/api/data', fetcher);
Visuele vergelijking van de soorten cache
| Soort cache | Waar het werkt | Besturing | Voorbeeld |
|---|---|---|---|
| Volledige routecache | Server (hele route) | revalidate, tag | export const revalidate = 60 |
| Datacache | Server (fetch) | cache, revalidate | fetch(..., { next: ... }) |
| Statisch gegenereerde cache | Server (buildtijd) | Geen | JSX zonder data |
| Cache aan de clientzijde | Browser / client | Jij | SWR / React Query |
Voorbeeld uit het dagelijks leven
Stel dat je een webshop bouwt:
| Pagina | Wat te onthouden | Welke soort cache te gebruiken |
|---|---|---|
/producten | Productlijst, elke 5 minuten bijgewerkt | Volledige routecache (ISR) |
/api/producten | Productdata van de API | Datacache (met revalidateTag) |
| SPA-navigatie | Snelle navigatie | Routercache (cache aan de clientzijde) |
Veelvoorkomende cacheproblemen in de App Router
Data is bijgewerkt, maar de pagina toont oude data
fetch() gebruikt standaard force-cache als je geen cache: 'no-store' of next: { revalidate } opgeeft. Het gevolg: Next.js onthoudt de data en werkt haar nooit bij.
Oplossing:
- Voeg
cache: 'no-store'toe om caching uit te schakelen - Of
next: { revalidate: 60 }voor ISR
fetch('https://api.com/data', { next: { revalidate: 60 } });
De URL (searchParams) is veranderd, maar de data blijft hetzelfde
Next.js voert de fetch-aanvraag niet opnieuw uit als de URL gelijk is. Gebruik je parameters, zet ze dan rechtstreeks in de fetch-URL:
fetch(`https://api.com/posts?category=${searchParams.category}`, {
next: { revalidate: 60 }
});
Navigatie met <Link>, maar de data is niet bijgewerkt
De routercache (cache aan de clientzijde) is gebruikt. Next.js stuurt geen nieuwe request wanneer je terugkeert naar de pagina.
Oplossing:
- Forceer verversing van de data met
router.refresh() - Gebruik
cache: 'no-store'als de data altijd vers moet zijn
revalidatePath() of revalidateTag() werkt niet
- De cache is niet aangemaakt (bijvoorbeeld door
no-store) - Je gebruikt geen
tagsin de fetch - Je roept
revalidatePath()aan buiten een server action
Oplossing:
- Zorg dat de fetch
next: { tags: [...] }gebruikt - Roep
revalidateTag('tag')aan in een serverfunctie
De layout wordt gecachet en wordt niet bijgewerkt
layout.tsx wordt ook gecachet als RSC met revalidate, en als de data erin gecachet is, wordt die pas bijgewerkt bij de volgende ISR.
Oplossing:
- Gebruik
cache: 'no-store'voor fetches in layout.tsx, als die bijvoorbeeld een gebruikersprofiel bevat - Of verplaats het ophalen van de layout naar de pagina (of naar een clientcomponent met SWR)
Onverwacht gedrag in de ontwikkelmodus (dev)
In next dev staan veel caches uit of werken ze anders, en revalidate werkt niet betrouwbaar in de ontwikkelmodus.
Tip:
Test caching met next build && next start of op een Vercel-preview.
Een combinatie van cookies(), headers(), params en fetch() breekt de cache.
Wanneer je dynamische dingen gebruikt zoals:
cookies()
headers()
useSearchParams()
generateMetadata() met dynamische waarden
schakelt Next.js de paginacache automatisch uit en gaat over op dynamic = 'force-dynamic'. Checklist: zo vermijd je cacheproblemen
Stel het fetchgedrag altijd duidelijk in:
- Vergeet niet de queryparameters rechtstreeks in de fetch-URL op te nemen
// no cache at all
fetch(..., { cache: 'no-store' })
// ISR (revalidate every 60 seconds)
fetch(..., { next: { revalidate: 60 } })
// manual invalidation via tag
fetch(..., { next: { tags: ['products'] } })
- Vergeet niet de queryparameters rechtstreeks in de URL te zetten die je aan fetch doorgeeft
- Test met
next build && next start, anders kun je een vals gevoel van zekerheid hebben - Moet je data handmatig ongeldig maken, gebruik dan
revalidatePath()ofrevalidateTag()
Draait de cache in je project met de App Router al in productie en geeft hij problemen? Dat soort klussen pak ik op als freelance Next.js-developer: cache-audits, fixes en migraties naar de App Router.