revalidateTag vs revalidatePath: Cache-invalidering i Next.js 15/16 (2026)

revalidateTag rydder tag-baserede fetch-kald på tværs af appen; revalidatePath tømmer én rutes cache. Guide med kode, faldgruber og webhook-mønstre til Next.js 15/16.

Opdateret: 22. august 2026

revalidateTag invaliderer alle fetch-kald, der er tagget med det samme strengnavn, mens revalidatePath invaliderer den fulde rutecache (og de fetch-data, ruten hentede) for en konkret sti. I Next.js 15/16 betyder det, at du bruger revalidateTag, når dine data logisk hører sammen på tværs af flere sider, og revalidatePath, når du vil tvinge én bestemt rute, eller alle ruter i en layout-familie, til at rendere igen. Denne guide viser hvornår, hvorfor og hvordan.

  • revalidateTag(tag) tømmer Data Cache for alle fetch()-kald med det pågældende tag; du kan ramme mange endepunkter og sider med ét kald.
  • revalidatePath(path, type?) tømmer både Data Cache og Full Route Cache for én sti; med 'layout' rammer du hele undertræet.
  • Ingen af dem kører synkront: de markerer cachen som stale, og næste request re-renderer på serveren.
  • Fra Next.js 15.5 er unstable_cache deprecated til fordel for det nye 'use cache'-direktiv med cacheTag() og cacheLife().
  • Kald altid revalideringsfunktioner fra Server Actions eller Route Handlers, aldrig direkte fra en Server Component under render.
  • Webhooks fra Sanity, Contentful eller Stripe hører hjemme i en Route Handler, hvor du kombinerer revalidateTag med en delt secret.

Hvordan fungerer cachen i Next.js 15/16?

Så, før vi taler om invalidering, er det vigtigt at holde tungen lige i munden omkring de fire cachelag Next.js opererer med. Ærligt talt blander jeg stadig navnene sammen ind imellem, og jeg har skrevet Next.js siden pages-only dagene, så du er ikke alene, hvis det virker forvirrende. De fire lag er: Request Memoization (dedup af identiske fetch-kald i samme render), Data Cache (persistent på tværs af requests og deploys), Full Route Cache (hele HTML/RSC-payloaden for en statisk rute) og Router Cache (klient-side i browseren).

Både revalidateTag og revalidatePath arbejder på server-side lagene, primært Data Cache og Full Route Cache. Router Cache i browseren er en anden historie, og den styres blandt andet af router.refresh() og staleTimes-konfigurationen i next.config.js. Med Next.js 15 blev standardadfærden for fetch() uncached, hvilket betyder at du skal opt-in med { next: { tags: [...] } } for at bruge tag-baseret invalidering. Det er en stor ændring fra pages-router-æraen, hvor ISR primært handlede om revalidate-nøglen i getStaticProps.

Den mentale model, der har hjulpet mig mest: tænk på tags som labels på post-it-sedler på hver enkelt cacheindgang, og på paths som adresser på hele mapper. Når du river en post-it af, fjerner du én type indhold på tværs af mapperne. Når du rydder en mappe, ryger alt indeni, uanset hvilke sedler der var sat på.

revalidateTag forklaret med kode

revalidateTag() er et tag-baseret invalideringsværktøj introduceret sammen med App Router og gjort førsteklasses i Next.js 14+. Du markerer først et fetch-kald med et eller flere tags:

// app/products/page.tsx
export default async function ProductsPage() {
  const res = await fetch('https://api.eksempel.dk/products', {
    next: { tags: ['products'] },
  })
  const products = await res.json()

  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  )
}

Alle steder i appen (layouts, sider, komponenter), hvor du henter fra samme URL med samme tag, deler samme cache-entry. Når du så kalder revalidateTag('products'), invalideres den entry, og næste request henter friske data. Bemærk: kaldet trigger ikke en render med det samme; det markerer blot cachen som stale.

// app/actions/refresh.ts
'use server'
import { revalidateTag } from 'next/cache'

export async function refreshProducts() {
  // ...måske en mutation her: opret produkt, opdater lager osv.
  revalidateTag('products')
}

Styrken ligger i, at du kan give flere tags til ét fetch-kald. Eksempel: en produktside kan hente både ['products', 'product:42'], så både en enkelt-produkt-mutation og en global "opdater alt katalog"-webhook kan ramme det. Tags er blot strenge, og der er ingen navneplads, så jeg anbefaler et konsistent format som type:id, fx user:123 eller order:abc-xyz. Dokumentationen i Next.js API reference for revalidateTag giver flere eksempler og edge cases.

revalidatePath forklaret med kode

revalidatePath() arbejder på rute-niveau. Den invaliderer både Data Cache for alle fetch-kald udført under rendering af den pågældende rute og Full Route Cache-entrien for selve HTML/RSC-payloaden. Signaturen er revalidatePath(path: string, type?: 'page' | 'layout').

'use server'
import { revalidatePath } from 'next/cache'

export async function updateOrder(orderId: string) {
  // muter i din database
  await db.orders.update({ where: { id: orderId }, data: { status: 'shipped' } })

  // invaliderer /dashboard/orders og alle dens data
  revalidatePath('/dashboard/orders')
}

Sender du 'layout' som andet argument, invalideres hele undertræet under den pågældende layout. Det er nyttigt, hvis du har et delt sidebar-layout, der viser tællere, som skal opdateres på alle undersider:

revalidatePath('/dashboard', 'layout')
// invaliderer /dashboard, /dashboard/orders, /dashboard/settings, osv.

For dynamiske ruter angiver du den bogstavelige filsti, ikke den udrullede URL. Altså revalidatePath('/products/[slug]', 'page'), ikke revalidatePath('/products/basketball-shoes'). Det er en klassisk pages-router-vane, der ikke længere gælder, og den har snydt mig flere gange, når jeg debugger stale sider i produktion. Den officielle revalidatePath-reference har den detaljerede signatur og en oversigt over edge cases.

Sammenligning: hvornår vælger du hvad?

Her er en direkte side-om-side sammenligning af de to funktioner, så du hurtigt kan afgøre, hvilken der passer bedst til dit use case.

DimensionrevalidateTagrevalidatePath
GranularitetTag på fetch-kald (kan ramme mange sider)Én sti (eller ét layout-undertræ)
Invaliderer Data CacheJa, kun taggede fetch-kaldJa, alle fetch-kald under stien
Invaliderer Full Route CacheKun ruter, der brugte det taggede fetchJa, den valgte sti eller layout-undertræ
Kræver forudgående setupJa, tags skal sættes på fetchNej, virker med det samme
Bedst tilCross-cutting data (fx alle produkter, én bruger)Kendte, konkrete sider (dashboard, ordreliste)
Typisk fraWebhooks, mutation-endepunkter, batchjobsServer Actions efter en formularindsendelse
Support i 'use cache'Ja, via cacheTag()Ja, men mere begrænset (se nedenfor)

Min tommelfingerregel efter et par års produktionsbrug: start med revalidatePath, når du bygger et CRUD-flow, hvor mutationen sker på samme side, brugeren ser. Skift til revalidateTag, når de samme data vises på flere sider, når mutationen sker et andet sted end visningen, eller når data skal opdateres via webhook fra en headless CMS. Kombiner dem gerne. Der er ingen straf for at kalde begge i samme Server Action.

Det nye 'use cache'-direktiv og cacheTag

Next.js 15.5 introducerede det stabile 'use cache'-direktiv (under en flag), og Next.js 16 har gjort det til den anbefalede måde at cache på for nye projekter. Det udfaser unstable_cache og giver dig mere finkornet kontrol over cachelifetime pr. funktion. Jeg har en hel gennemgang af det i vores guide til use cache-direktivet, men det korte er, at du nu skriver:

import { unstable_cacheTag as cacheTag, unstable_cacheLife as cacheLife } from 'next/cache'

async function getProducts() {
  'use cache'
  cacheTag('products')
  cacheLife('hours')

  const res = await fetch('https://api.eksempel.dk/products')
  return res.json()
}

Fordelen er, at hele funktionen (ikke bare fetch-kaldet) bliver cached, hvilket også dækker beregninger, JSON-parsing og evt. transformationer. Og revalidateTag('products') virker præcis som før: den invaliderer alle cacheTag('products')-entries. For revalidatePath gælder det stadig, at den invaliderer den fulde rutecache, men den rammer ikke nødvendigvis 'use cache'-funktioner uden et matching tag. Vær derfor eksplicit med tags, hvis dine data lever i use cache-blokke.

On-demand revalidation fra webhooks

Et af de hyppigste real-world scenarier: du bruger Sanity, Contentful, Payload eller Strapi som headless CMS, og du vil kun re-rendere sider, når indholdet faktisk ændrer sig. Løsningen er en Route Handler, der modtager webhooks og kalder revalidateTag. Jeg dækker mønstre for API-endepunkter i vores guide til Route Handlers, men her er en minimal, sikker implementering:

// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  const secret = request.headers.get('x-webhook-secret')
  if (secret !== process.env.CMS_WEBHOOK_SECRET) {
    return NextResponse.json({ ok: false }, { status: 401 })
  }

  const body = await request.json()
  const tag = body?._type // fx 'product', 'article', 'author'
  if (!tag) {
    return NextResponse.json({ ok: false, error: 'no tag' }, { status: 400 })
  }

  revalidateTag(tag)
  return NextResponse.json({ ok: true, revalidated: tag })
}

Vigtigt: verificer altid en shared secret (eller signatur, hvis udbyderen understøtter HMAC). En åben revalidate-endpoint er en gratis DoS-vektor, og én ondsindet request kan udløse regenerering af hele dit site. For Stripe- og GitHub-webhooks bør du bruge deres signaturvalidering fremfor en simpel shared secret. Jeg ramte selv den her fælde, da et projekt af mig blev spammed af nogen, der havde fundet /api/revalidate uden auth.

Revalidering fra Server Actions

Server Actions er, hvor de fleste udviklere først møder revalidering. Efter en form-submit vil du typisk have Next.js til at re-rendere den side, brugeren ser. Vores deep-dive i Server Actions går i detaljer, men kernemønsteret er dette:

// app/todos/actions.ts
'use server'
import { revalidateTag, revalidatePath } from 'next/cache'
import { z } from 'zod'

const CreateTodo = z.object({ title: z.string().min(1).max(200) })

export async function createTodo(formData: FormData) {
  const parsed = CreateTodo.safeParse({
    title: formData.get('title'),
  })
  if (!parsed.success) {
    return { error: 'Ugyldig input' }
  }

  await db.todo.create({ data: parsed.data })

  // ramt begge lag, sikker og eksplicit
  revalidateTag('todos')
  revalidatePath('/todos')

  return { ok: true }
}

Bemærk et par ting: (1) kald revalideringsfunktionerne efter mutationen, aldrig før, ellers henter du gamle data igen. (2) Return-værdien fra en Server Action kan bruges sammen med useActionState til fejlfeedback. (3) Hvis din komponent bruger useOptimistic, viser UI'en den optimistiske tilstand med det samme, mens revalideringen kører i baggrunden.

Faldgruber og typiske fejl

Efter at have skiftet en pages-router-baseret app til App Router (og skrevet et sats blogposts undervejs) er her de faldgruber, jeg selv er trådt i eller har set kolleger falde i:

1. Kald af revalidate under render

Det er en runtime-fejl at kalde revalidateTag/revalidatePath fra en Server Component under render. Kaldet skal ske i en 'use server'-funktion (Server Action) eller i en Route Handler. Fejlen ser sådan ud: Route ... used revalidateTag ... which is not allowed in a Client Component or during render.

2. Forventet synkron opførsel

revalidateTag markerer cachen som stale, men det næste request re-renderer. Bruger du router.refresh() på klienten efter en Server Action, får du opdateringen med det samme; ellers ser brugeren måske stadig gammelt indhold, indtil de navigerer.

3. Dynamiske ruter med udrullet path

Som nævnt: brug filsti-formatet /products/[slug], ikke den konkrete URL. For at invalidere en enkelt konkret dynamisk instans er tag den bedre vej: giv fetch tags: ['product:' + slug] og kald derefter revalidateTag('product:' + slug).

4. Glemte tags på 'use cache'-funktioner

En cached funktion uden cacheTag() kan kun invalideres via revalidatePath for den rute, der bruger den, eller ved fuld deploy. Sæt altid mindst ét tag.

5. Router Cache i browseren

Selv efter en serverside revalidation kan browseren stadig vise cached RSC-payload i op til staleTimes.dynamic sekunder. I Next.js 15 er default ret aggressiv; jeg sætter typisk staleTimes.dynamic: 30 i next.config.js for at balancere UX og friskhed.

Måling og debugging i produktion

Cache-adfærd er notorisk svær at debugge, fordi problemet ofte manifesterer sig som "den ene bruger, der ikke får opdatering". Her er værktøjer og teknikker, jeg selv støtter mig til:

  1. next.config.js logging: aktivér logging: { fetches: { fullUrl: true, hmrRefreshes: true } } lokalt for at se hvilke fetch-kald der cachedes.
  2. Response headers: Vercel returnerer x-vercel-cache: HIT|MISS|STALE|REVALIDATED, som er guld værd i browserens netværksfane.
  3. Custom wrapper: pak revalidateTag ind i en lille helper, der logger til Datadog/Sentry før det rigtige kald.
  4. Draft Mode: brug Draft Mode til at teste, om et bestemt fetch overhovedet blev cachet, uafhængigt af tags.
  5. Instrumentation: instrumentation.ts med OpenTelemetry giver spans for cachehits. Se den seneste Next.js 15.5 changelog for de nye eksporter.

Og hold øje med, om du sidder på en pages-router-vane. Jeg fanger mig selv i at ville skrive res.revalidate(), som ikke findes i App Router. Alle on-demand invalideringer går gennem revalidateTag eller revalidatePath, punktum.

Ofte stillede spørgsmål

Hvad er forskellen på revalidateTag og revalidatePath?

revalidateTag invaliderer cache-entries på tværs af hele appen ud fra en streng-tag, du selv har sat på fetch- eller use cache-kald. revalidatePath invaliderer én konkret rute (eller et layout-undertræ) inklusive alle fetch-data brugt af den rute. Tags er cross-cutting; paths er lokaliseret.

Kan jeg bruge revalidateTag i en Server Component?

Nej. Kaldet er kun tilladt i Server Actions ('use server'-funktioner) og Route Handlers. Kaldes det under render, kaster Next.js en runtime-fejl. Brug en Server Action, som du så invoker via en formular, eller flyt logikken til en Route Handler, hvis den udløses af en webhook.

Revalidator revalidatePath alle undersider automatisk?

Ikke som standard. revalidatePath('/dashboard') rammer kun præcis /dashboard. Vil du invalidere alle undersider under et layout, skal du sende 'layout' som andet argument: revalidatePath('/dashboard', 'layout'). Bemærk at dette potentielt kan trigge mange regeneringer.

Hvordan invaliderer jeg cache fra en webhook?

Opret en Route Handler (typisk app/api/revalidate/route.ts), verificer webhook-secret'et, og kald revalidateTag(tag) baseret på payload'ens indholdstype. Undgå at eksponere et åbent endpoint uden auth. Det er en trivial DoS-vektor mod dine build-minutter.

Erstatter 'use cache' revalidateTag og revalidatePath?

Nej. 'use cache'-direktivet er en ny måde at oprette cached funktioner på, men invalideringen sker stadig via revalidateTag (eller revalidatePath). Kombinationen er cacheTag('label') inde i funktionen, og senere revalidateTag('label') for at rydde den.

Ben Howard
Om Forfatteren Ben Howard

Full-stack Next.js developer who's been with the framework since pages-only days. Slowly warming up to App Router.