Next.js use cache: Komplet Guide til Moderne Caching

Lær at bruge Next.js 16 use cache direktivet til at cache funktioner, komponenter og hele sider. Guide med cacheLife, cacheTag, PPR-integration og migrering fra unstable_cache.

Introduktion: Caching i Next.js Har Endelig Fået en Makeover

Okay, lad os bare sige det som det er — caching i Next.js har været et af de mest frustrerende emner for udviklere i årevis. Mellem fetch-cache, Data Cache, Full Route Cache og Router Cache var det nærmest et gætteri at forudsige, hvad der blev cachet hvornår. Jeg har selv brugt timer på at debugge cache-relaterede problemer, der viste sig at skyldes en misforstået standardindstilling.

Med Next.js 16 har Vercel heldigvis taget konsekvensen og introduceret en helt ny tilgang: use cache direktivet.

Det erstatter det nu forældede unstable_cache og giver en langt mere intuitiv måde at styre caching på. Tænk på det som en naturlig forlængelse af use client og use server — et simpelt direktiv der markerer en funktion, komponent eller hel side som cachebar. Sammen med cacheLife til styring af levetid og cacheTag til on-demand invalidering har du nu reel kontrol over din caching-strategi.

I denne guide gennemgår vi alt du behøver at vide om use cache i Next.js 16.2 — fra opsætning til avancerede mønstre, inklusive migrering fra unstable_cache og integration med Partial Prerendering (PPR). Så lad os dykke ned i det.

Aktivering af Cache Components

Før du kan bruge use cache, skal du aktivere Cache Components i din Next.js-konfiguration. Det er heldigvis ret simpelt — tilføj cacheComponents: true til din next.config.ts:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,
}

export default nextConfig

Når dette er aktiveret, kan du bruge use cache direktivet overalt i din applikation. GET Route Handlers følger også den samme prerendering-model som sider, når Cache Components er slået til.

Sådan Bruges use cache: Tre Niveauer

use cache direktivet kan anvendes på tre forskellige niveauer, afhængigt af hvor bredt du vil cache. Lad os gennemgå dem ét ad gangen.

Fil-niveau: Cache Alle Eksporter

Placér use cache øverst i filen for at cache alle eksporterede funktioner. Det er den bredeste tilgang:

'use cache'

export default async function Page() {
  const products = await db.query('SELECT * FROM products')
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  )
}

export async function ProductCount() {
  const count = await db.query('SELECT COUNT(*) FROM products')
  return <span>{count}</span>
}

Begge komponenter caches her. En vigtig detalje: alle eksporterede funktioner i filen skal være asynkrone.

Komponent-niveau: Cache Individuelle Komponenter

Hvis du kun vil cache en bestemt komponent, kan du tilføje direktivet direkte i den:

export async function BlogPosts({ category }: { category: string }) {
  'use cache'
  const posts = await fetch(`/api/posts?category=${encodeURIComponent(category)}`)
  const data = await posts.json()
  return (
    <section>
      {data.map((post: any) => (
        <article key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </section>
  )
}

Denne komponent caches separat, og det smarte er at cache-nøglen automatisk inkluderer category-prop'en. Forskellige kategorier giver separate cache-poster — uden at du skal konfigurere noget.

Funktions-niveau: Cache Datahentning

Du kan også bruge use cache på individuelle funktioner. Det er nok min personlige favorit, fordi det er perfekt til databaseforespørgsler og API-kald:

import { cacheLife } from 'next/cache'

export async function getUsers() {
  'use cache'
  cacheLife('hours')
  return db.query('SELECT * FROM users')
}

export async function getProductById(id: string) {
  'use cache'
  cacheLife('days')
  return db.query('SELECT * FROM products WHERE id = $1', [id])
}

Data-niveau caching er særligt nyttigt når de samme data bruges på tværs af flere komponenter, eller når du vil cache data uafhængigt af selve UI'et.

Sådan Fungerer Cache-nøgler

Ærligt talt er dette en af de mest velkomne forbedringer. Med unstable_cache skulle du manuelt angive nøgler via et keyParts-array, og den mindste fejl i nøglerne førte til svære bugs. Med use cache genereres nøglerne automatisk baseret på:

  • Build ID — Unikt per build. Nye deployments invaliderer automatisk alle cache-poster.
  • Funktions-ID — Et sikkert hash af funktionens placering og signatur i kodebasen.
  • Serialiserbare argumenter — Props (for komponenter) eller funktionsargumenter.
  • Variabler fra ydre scopes — Lukkede variabler fanges automatisk og indgår i nøglen.
async function Component({ userId }: { userId: string }) {
  const getData = async (filter: string) => {
    'use cache'
    // Cache-nøglen inkluderer BÅDE userId (fra closure) og filter (argument)
    return fetch(`/api/users/${userId}/data?filter=${filter}`)
  }

  return getData('active')
}

Her bliver userId fra det ydre scope automatisk en del af cache-nøglen sammen med filter-argumentet. Forskellige kombinationer af bruger og filter får hver deres cache-post — helt uden at du skal tænke over det. Ret elegant, faktisk.

Styring af Cache-levetid med cacheLife

Som standard bruger use cache en default-profil med 5 minutters stale-tid (klient-side), 15 minutters revalidate-interval (server-side), og cachen udløber aldrig af sig selv.

De standardværdier passer sjældent til alle scenarier, og det er her cacheLife kommer ind i billedet.

Indbyggede Profiler

Next.js leverer flere foruddefinerede cache-profiler, som dækker de mest almindelige behov:

import { cacheLife } from 'next/cache'

// Blog-indlæg der opdateres dagligt
export async function getBlogPosts() {
  'use cache'
  cacheLife('days')
  return fetch('/api/posts')
}

// Konfiguration der sjældent ændres
export async function getSiteConfig() {
  'use cache'
  cacheLife('weeks')
  return fetch('/api/config')
}

// Data der skal være frisk inden for minutter
export async function getNotifications() {
  'use cache'
  cacheLife('minutes')
  return fetch('/api/notifications')
}

Brugerdefinerede Profiler

Har du brug for mere kontrol? Du kan definere dine egne cache-profiler i next.config.ts med tre timing-egenskaber: stale (hvor længe klienten bruger cached data uden at tjekke serveren), revalidate (hvor ofte serveren genvaliderer), og expire (hvornår cachen udløber helt):

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,
  cacheLife: {
    produktkatalog: {
      stale: 60 * 60,           // 1 time — klienten bruger cached data
      revalidate: 60 * 15,      // 15 minutter — serveren genvaliderer
      expire: 60 * 60 * 24,     // 24 timer — cachen udløber helt
    },
    brugerdata: {
      stale: 60 * 5,            // 5 minutter
      revalidate: 60,           // 1 minut
      expire: 60 * 60,          // 1 time
    },
  },
}

export default nextConfig

Derefter bruger du bare profilnavnet direkte i dine funktioner:

import { cacheLife } from 'next/cache'

export async function getProducts() {
  'use cache'
  cacheLife('produktkatalog')
  return db.query('SELECT * FROM products WHERE active = true')
}

Betinget Cache-levetid

En smart mulighed er at anvende forskellige cache-levetider baseret på betingelser. For eksempel kan du cache kortvarigt når indhold mangler, og længere når det er publiceret:

import { cacheLife, cacheTag } from 'next/cache'

export async function getArticle(slug: string) {
  'use cache'
  cacheTag(`article-${slug}`)

  const article = await db.query(
    'SELECT * FROM articles WHERE slug = $1', [slug]
  )

  if (!article) {
    cacheLife('minutes')  // Cache kort tid — artiklen findes måske snart
    return null
  }

  cacheLife('days')       // Cache længere for publiceret indhold
  return article
}

Én ting at huske: kun ét cacheLife-kald bør udføres per funktionskald. Du kan have dem i forskellige kontrolflow-grene, men sørg for at kun én gren rent faktisk kører per invokation.

On-demand Invalidering med cacheTag

cacheTag lader dig tilknytte tags til cache-poster, så du selektivt kan invalidere specifikke data uden at påvirke andre dele af cachen. Det er virkelig kraftfuldt i kombination med Server Actions.

import { cacheTag, cacheLife } from 'next/cache'

// Datahentning med cache-tags
export async function getProducts() {
  'use cache'
  cacheLife('hours')
  cacheTag('products')
  return db.query('SELECT * FROM products')
}

export async function getProductById(id: string) {
  'use cache'
  cacheLife('days')
  cacheTag('products', `product-${id}`)
  return db.query('SELECT * FROM products WHERE id = $1', [id])
}

Og her er det smarte — du invaliderer cachen via en Server Action når data ændres:

'use server'

import { updateTag } from 'next/cache'

export async function createProduct(formData: FormData) {
  const title = formData.get('title') as string
  const price = parseFloat(formData.get('price') as string)

  await db.query(
    'INSERT INTO products (title, price) VALUES ($1, $2)',
    [title, price]
  )

  // Invalidér alle cache-poster med 'products'-tagget
  updateTag('products')
}

export async function updateProduct(id: string, formData: FormData) {
  const title = formData.get('title') as string

  await db.query(
    'UPDATE products SET title = $1 WHERE id = $2',
    [title, id]
  )

  // Invalidér kun den specifikke produkts cache
  updateTag(`product-${id}`)
}

Bemærk brugen af updateTag i stedet for revalidateTag — i Next.js 16.2 er updateTag den foretrukne metode til øjeblikkelig cache-invalidering.

De Tre Cache-varianter

Next.js tilbyder tre varianter af use cache direktivet, og de dækker ret forskellige behov. Det kan virke lidt overvældende i starten, men det giver god mening når man tænker over det.

use cache (Standard — In-memory)

Standard-varianten gemmer cache-poster i hukommelsen via en LRU-algoritme (Least Recently Used). Det er hurtigt og kræver ingen ekstern infrastruktur, men cache-poster kan blive fjernet under hukommelsespres, og de overlever ikke server-genstarter.

export async function getData() {
  'use cache'
  return fetch('/api/data')
}

I serverless-miljøer som Vercel persists cache-poster typisk ikke mellem requests, da hver invokation kan køre i en ny instans. Build-time caching fungerer dog stadig normalt.

use cache: remote (Delt/Persistent)

Når in-memory caching ikke rækker, kan du bruge use cache: remote til at gemme cache-poster i en ekstern cache-handler som Redis eller en KV-database:

export async function getGlobalConfig() {
  'use cache: remote'
  return fetch('/api/config')
}

Fordelen er at cachen deles på tværs af alle server-instanser og overlever genstarter. Ulempen? Det kræver et netværksopkald og medfører typisk platformsgebyrer.

use cache: private (Browser-only)

Til brugerspecifik caching der kræver adgang til runtime-API'er som cookies(), findes use cache: private:

export async function getUserPreferences() {
  'use cache: private'
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')?.value || 'light'
  return { theme }
}

Resultaterne gemmes kun i browserens hukommelse og persists hverken på serveren eller på tværs af sideopdateringer. Vær opmærksom på at denne variant ikke er tilgængelig i Route Handlers.

Integration med Partial Prerendering (PPR)

use cache er tæt integreret med Partial Prerendering, som er den nye standard-renderingsmodel i Next.js med Cache Components. PPR kombinerer statisk og dynamisk indhold i samme rute, og det fungerer sådan her:

  • Komponenter markeret med use cache inkluderes i den statiske shell.
  • Komponenter pakket i <Suspense> streames ved request-tid.
  • Deterministiske operationer (rene beregninger, modul-imports) inkluderes automatisk i den statiske shell.

Her er et komplet eksempel der viser alle tre i samspil:

import { Suspense } from 'react'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag, updateTag } from 'next/cache'

export default function DashboardPage() {
  return (
    <>
      {/* Statisk indhold — prerenderes automatisk */}
      <header>
        <h1>Dashboard</h1>
        <nav>Hjem | Profil | Indstillinger</nav>
      </header>

      {/* Cached dynamisk indhold — del af den statiske shell */}
      <StatistikPanel />

      {/* Runtime dynamisk indhold — streames ved request-tid */}
      <Suspense fallback={<p>Indlæser dine præferencer...</p>}>
        <BrugerPræferencer />
      </Suspense>
    </>
  )
}

// Alle brugere ser de samme statistikker (genvalideres hver time)
async function StatistikPanel() {
  'use cache'
  cacheLife('hours')
  cacheTag('statistik')

  const stats = await fetch('https://api.example.com/stats')
  const data = await stats.json()

  return (
    <section>
      <h2>Seneste Statistik</h2>
      <div>Besøgende: {data.visitors}</div>
      <div>Sidevisninger: {data.pageviews}</div>
    </section>
  )
}

// Personaliseret per bruger baseret på cookies
async function BrugerPræferencer() {
  const tema = (await cookies()).get('theme')?.value || 'light'
  return <aside>Dit tema: {tema}</aside>
}

Under prerendering bliver headeren (statisk) og statistikpanelet (cachet med use cache) en del af den statiske shell, sammen med fallback-UI'et for brugerpræferencer. Kun de personaliserede præferencer streames ind ved request-tid. Det giver en fantastisk brugeroplevelse med nærmest øjeblikkelig initial load.

Arbejde med Runtime-API'er

En vigtig begrænsning du skal kende til: du kan ikke tilgå runtime-API'er som cookies(), headers() eller searchParams direkte inde i et cachet scope. Det giver god mening, når man tænker over det — hvis resultatet er cachet, kan det jo ikke afhænge af runtime-værdier.

Løsningen er enkel: læs værdierne udenfor og send dem som argumenter.

import { cookies } from 'next/headers'
import { Suspense } from 'react'

export default function Page() {
  return (
    <Suspense fallback={<div>Indlæser...</div>}>
      <ProfilIndhold />
    </Suspense>
  )
}

// Ikke-cachet komponent der læser runtime-data
async function ProfilIndhold() {
  const session = (await cookies()).get('session')?.value
  return <CachedProfil sessionId={session} />
}

// Cachet komponent der modtager den ekstraherede værdi som prop
async function CachedProfil({ sessionId }: { sessionId: string | undefined }) {
  'use cache'
  if (!sessionId) return <p>Ikke logget ind</p>

  const bruger = await fetchUserData(sessionId)
  return <div>Velkommen, {bruger.name}</div>
}

sessionId bliver automatisk en del af cache-nøglen, så forskellige brugere får stadig hver deres cachede resultat.

Interleaving: Kombination af Cached og Dynamisk Indhold

Et af de mest elegante mønstre med use cache er det der kaldes interleaving. Det lader dig pakke dynamisk indhold ind i en cachet komponent via children eller slots:

async function CachedWrapper({ children }: { children: React.ReactNode }) {
  'use cache'
  const cachedData = await fetch('/api/cached-data')
  const data = await cachedData.json()
  return (
    <div>
      <header>Sidst opdateret: {data.lastUpdate}</header>
      {children}  {/* Dynamisk indhold passerer igennem */}
    </div>
  )
}

// Brug
export default function Page() {
  return (
    <CachedWrapper>
      <Suspense fallback={<p>Indlæser...</p>}>
        <DynamiskKomponent />  {/* Ikke cachet, sendt igennem */}
      </Suspense>
    </CachedWrapper>
  )
}

Så længe du ikke læser eller modificerer children inde i den cachede funktion, påvirker det ikke cache-posten. Du kan altså cache den omkringliggende struktur, mens det indlejrede indhold forbliver helt dynamisk. Ret smart.

Migrering fra unstable_cache til use cache

Hvis du opgraderer fra Next.js 15 til 16, er det på tide at sige farvel til unstable_cache. Her er et direkte sammenligningseksempel, så du kan se forskellen:

Før (Next.js 15 med unstable_cache)

import { unstable_cache } from 'next/cache'

export const getCachedUser = (userId: string) => {
  return unstable_cache(
    async () => db.users.findUnique({ where: { id: userId } }),
    ['user-cache-key', userId],
    { tags: [`user-${userId}`], revalidate: 3600 }
  )()
}

Efter (Next.js 16 med use cache)

import { cacheTag, cacheLife } from 'next/cache'

export async function getUserProfile(userId: string) {
  'use cache'
  cacheLife('hours')
  cacheTag(`user-${userId}`)
  return db.users.findUnique({ where: { id: userId } })
}

Den nye kode er markant renere. Her er de vigtigste forskelle:

  • Ingen manuelle cache-nøgler — argumenter og lukkede variabler bliver automatisk del af nøglen.
  • Direkte funktion — ingen wrapping i en factory-funktion (ja, det var irriterende).
  • Deklarativ levetidcacheLife erstatter den gamle revalidate-indstilling.
  • Bredere scopeuse cache kan cache funktioner OG komponenter, ikke kun data.

Fejlfinding og Debugging

Verbose Logging

Hvis du har brug for at se hvad der foregår under motorhjelmen, kan du aktivere detaljerede cache-logs med en miljøvariabel:

NEXT_PRIVATE_DEBUG_CACHE=1 npm run dev
# eller i produktion
NEXT_PRIVATE_DEBUG_CACHE=1 npm run start

I development vises console-logs fra cachede funktioner med et Cache-præfiks, som gør det nemt at skelne dem fra normale logs.

Build Hænger (Cache Timeout)

Hvis dit build hænger, skyldes det sandsynligvis at du tilgår Promises der resolver til uncached eller runtime-data, oprettet udenfor en use cache-grænse. Den cachede funktion venter på data der ikke kan resolves under build, og timeout'er efter 50 sekunder.

Almindelige årsager inkluderer:

  • At sende runtime-data Promises som props til en cachet komponent.
  • At tilgå dem via closure fra en delt Map eller lignende storage.

Løsningen er at afvente de dynamiske data udenfor det cachede scope og sende de konkrete værdier som argumenter i stedet.

Best Practices for use cache

Her er nogle retningslinjer jeg har samlet efter at have arbejdet med use cache i praksis:

  • Start med data-niveau caching — cache individuelle datahentningsfunktioner frem for hele sider, medmindre hele siden rent faktisk er statisk.
  • Vælg passende cacheLife-profiler — brug minutes for volatile data, hours for semi-statisk data, og days/weeks for sjældent ændrede data.
  • Brug meningsfulde cache-tags — strukturér dine tags hierarkisk (f.eks. products og product-123) for granulær invalidering.
  • Ekstraher runtime-data — læs altid cookies() og headers() udenfor cachede scopes.
  • Overvej serverless-begrænsninger — i serverless-miljøer bør du bruge use cache: remote for data der skal deles på tværs af instanser.
  • Undgå at cache ekstremt volatile data — for realtidsdata som notifikationstællere giver server-side caching ofte mere kompleksitet end det er værd.

Ofte Stillede Spørgsmål

Hvad er forskellen mellem use cache og fetch med force-cache?

use cache er et langt bredere koncept. Mens fetch med force-cache kun cacher HTTP-anmodninger, kan use cache cache alt — databaseforespørgsler, beregninger, hele komponenter og endda komplette sider. Du får også mere kontrol med cacheLife og cacheTag.

Kan jeg bruge use cache med cookies og headers?

Ikke direkte inde i det cachede scope, nej. Runtime-API'er som cookies() og headers() skal læses udenfor, og de konkrete værdier sendes som argumenter. Der er dog en undtagelse: use cache: private-varianten tillader direkte adgang til runtime-API'er, men cacher kun i browseren.

Hvad sker der med min cache ved et nyt deployment?

Da Build ID er en del af cache-nøglen, invalideres alle use cache-poster automatisk ved nye deployments. Det sikrer at brugere aldrig får forældet cachet indhold efter en release. For use cache: remote afhænger det af din konfigurerede cache-handler.

Er use cache klar til produktion?

Absolut. use cache blev introduceret som eksperimentel i Next.js 15 og er nu stabilt med Next.js 16.0. I den seneste version 16.2 (marts 2026) betragtes det som den anbefalede tilgang til caching og erstatter fuldt ud det forældede unstable_cache.

Hvordan påvirker use cache Partial Prerendering (PPR)?

PPR er faktisk standardadfærden når du bruger Cache Components. Komponenter markeret med use cache inkluderes i den statiske shell der serveres øjeblikkeligt, mens dynamiske komponenter pakket i <Suspense> streames ind efterfølgende. Resultatet er en nærmest øjeblikkelig initial load kombineret med dynamisk indhold.

Om Forfatteren Editorial Team

Our team of expert writers and editors.