Next.js Caching and Revalidation

Cache data in Next.js with use cache, set lifetimes, and refresh it on demand with tags.

TL;DR

  1. Enable cacheComponents, then add 'use cache' to cache an async function.
  2. Set the lifetime with cacheLife() and label data with cacheTag().
  3. Refresh data after a change with updateTag() or revalidatePath().

Cache Components

    cacheComponents

    Enable the caching model in your config; data stays dynamic until you cache it.

    import type { NextConfig } from 'next';
    
    const nextConfig: NextConfig = {
      cacheComponents: true,
    };
    
    export default nextConfig;
    'use cache'

    Cache an async function so calls with the same arguments reuse the result.

    export async function getProduct(id: string) {
      'use cache';
      const rows = await sql`
        SELECT * FROM products WHERE id = ${id}
      `;
      return rows[0];
    }
    Cached Component

    Cache a whole component, including the data it fetches and the markup it returns.

    export async function Footer() {
      'use cache';
      const links = await getFooterLinks();
      return <footer>{links.length} links</footer>;
    }

Lifetimes and Tags

    cacheLife()

    Pick a preset lifetime such as 'minutes', 'hours', 'days', or 'max'.

    import { cacheLife } from 'next/cache';
    
    export async function getPosts() {
      'use cache';
      cacheLife('hours');
      return sql`SELECT * FROM posts`;
    }
    Custom Lifetime

    Pass an object to set the stale, revalidate, and expire times in seconds.

    cacheLife({
      stale: 300,      // client reuse: 5 min
      revalidate: 900, // refresh after 15 min
      expire: 86400,   // drop after 1 day
    });
    cacheTag()

    Label cached data so you can refresh it by name later.

    import { cacheTag } from 'next/cache';
    
    export async function getPosts() {
      'use cache';
      cacheTag('posts');
      return sql`SELECT * FROM posts`;
    }

Refreshing on Demand

    updateTag()

    Expire tagged data immediately inside a Server Action, so the next render is fresh.

    'use server';
    import { updateTag } from 'next/cache';
    
    export async function addPost(form: FormData) {
      await savePost(form);
      updateTag('posts');
    }
    revalidateTag()

    Mark tagged data stale and refresh it in the background on the next visit.

    import { revalidateTag } from 'next/cache';
    
    export async function POST() {
      revalidateTag('posts', 'max');
      return Response.json({ ok: true });
    }
    revalidatePath()

    Refresh everything cached for one page or route by its URL.

    import { revalidatePath } from 'next/cache';
    
    revalidatePath('/blog');
    revalidatePath('/blog/[slug]', 'page');

Without Cache Components

    force-cache

    Cache a single request in apps that have not enabled Cache Components.

    const res = await fetch(url, {
      cache: 'force-cache',
      next: { tags: ['posts'] },
    });
    next.revalidate

    Refresh a cached request in the background after a number of seconds.

    const res = await fetch(url, {
      next: { revalidate: 3600 },
    });
    export const revalidate

    Rebuild a static page in the background on a timer, known as ISR.

    // app/blog/page.tsx
    export const revalidate = 60; // seconds

Tips

  1. Call updateTag() in a Server Action when the user must see their own change immediately on the next render.
  2. Pick a cacheLife() profile such as 'hours' or 'days' that matches how often the underlying data really changes.

Warnings

  1. Calling revalidateTag() with one argument is deprecated in Next.js 16, so pass a profile such as 'max' as the second.
  2. You cannot read cookies() or headers() inside a 'use cache' function, so read them outside and pass the values in.

In Practice

FAQ