DEV Community

Sachin Maurya
Sachin Maurya

Posted on

Caching & Revalidation in Next.js — ISR, Segment Options, and Tag-Based Revalidate (with real examples)

When people say “Next.js is fast,” they usually mean it caches smartly. If you learn how that cache works—and when to refresh it—you can take your app from pretty quick to feels instant.

This post is a practical guide I wish I had on day one. No fluff—just patterns I use in production.


0) A 60-second mental model

  • Static (SSG/ISR): HTML is generated ahead of time on the server/CDN.
  • Dynamic (SSR): HTML is generated on every request.
  • Revalidation: tell Next.js when to refresh previously cached HTML/data.
  • CDN: respects HTTP Cache-Control (e.g., s-maxage, stale-while-revalidate).

You mix these per route segment and per fetch call.


1) Route segment config: opt into static or dynamic

// app/dashboard/page.tsx
export const dynamic = 'force-static';          // or 'force-dynamic'
export const revalidate = 60;                   // ISR: re-generate at most once a minute
Enter fullscreen mode Exit fullscreen mode

When to use

  • force-static for content that changes rarely (blogs, docs).
  • revalidate for content that changes occasionally (marketing stats, pricing).

Gotcha: any use of cookies(), headers(), or searchParams may switch the route to dynamic. If you only need them client-side, move that logic to a client component.


2) Per-fetch caching: granular control

// Static + revalidate every 5 minutes
const res = await fetch('https://api.example.com/products', {
  next: { revalidate: 300 }
});
const products = await res.json();
Enter fullscreen mode Exit fullscreen mode
// Opt out of caching for this call only
const res = await fetch('https://api.example.com/profile', {
  cache: 'no-store'
});
Enter fullscreen mode Exit fullscreen mode
// Share cache across routes via tags
const res = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] }
});
Enter fullscreen mode Exit fullscreen mode

Rules of thumb

  • Use next.revalidate for APIs you trust to be stable.
  • Use cache: 'no-store' for per-user/private data.
  • Use tags when multiple pages depend on the same dataset.

3) Tag-based revalidation (server actions or route handlers)

When your CMS/back-office changes data, nudge Next.js to refresh only what depends on it.

// app/api/revalidate-posts/route.ts
import { revalidateTag } from 'next/cache';

export async function POST() {
  revalidateTag('posts');        // all fetch() using {tags:['posts']} refresh
  return Response.json({ revalidated: true });
}
Enter fullscreen mode Exit fullscreen mode

You can also revalidate a specific path:

import { revalidatePath } from 'next/cache';
// revalidatePath('/blog/my-article');
Enter fullscreen mode Exit fullscreen mode

Pro tip: secure this endpoint (secret header or token) before wiring it to your CMS webhook.


4) Incremental Static Regeneration (ISR) in the App Router

// app/blog/[slug]/page.tsx
export const revalidate = 600;     // regenerate at most once every 10 minutes

export async function generateStaticParams() {
  const slugs = await getAllSlugs();    // from CMS
  return slugs.map((slug) => ({ slug }));
}
Enter fullscreen mode Exit fullscreen mode

Why it’s nice

  • First visitor after revalidate window triggers regeneration in the background.
  • Everyone else keeps seeing the cached HTML.
  • Zero cron jobs.

5) HTTP cache headers for edge/CDN wins

For dynamic routes you still want CDN help:

// app/api/public-metrics/route.ts
export async function GET() {
  const data = await getMetrics();
  return Response.json(data, {
    headers: {
      'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300'
    }
  });
}
Enter fullscreen mode Exit fullscreen mode
  • s-maxage=60: CDN keeps it for 60s.
  • stale-while-revalidate=300: if stale, serve old copy while a fresh one is fetched.

Test quickly

curl -I https://yourdomain.com/api/public-metrics
Enter fullscreen mode Exit fullscreen mode

Check cache-control, age, etc.


6) Client data with SWR/React Query (and when not to)

Use a client cache when:

  • You need live-feeling UIs (polling, refetch-on-focus).
  • Data is user-specific.

Example with SWR:

'use client';
import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(r => r.json());

export default function Notifications() {
  const { data, error, isLoading } = useSWR('/api/notifications', fetcher, {
    refreshInterval: 15_000,     // poll every 15s
    revalidateOnFocus: true
  });

  // render states...
}
Enter fullscreen mode Exit fullscreen mode

Pair this with server-side ISR for the public parts of the page to keep TTFB low.


7) Avoid accidental cache busting

  • Passing ?timestamp=... to URLs kills cache.
  • Reading cookies()/headers() server-side marks the route dynamic.
  • Using no-store on a fetch makes the whole request chain uncached.

If only the client needs personalization, keep server components static and fetch user-specific data client-side.


8) Images, fonts, and assets (quick wins)

  • next/image already adds smart caching; also set a long s-maxage via your CDN.
  • Fonts: self-host with next/font, add <link rel="preconnect" href="https://fonts.gstatic.com" /> only if using external.
  • Static assets under /public are cacheable—serve with immutable, max-age=31536000.

9) A simple checklist

  • [ ] Mark static segments and set revalidate where sensible.
  • [ ] Add next.revalidate or tags on fetch calls.
  • [ ] Expose a secure /api/revalidate-* for CMS/webhooks.
  • [ ] Use CDN headers (s-maxage, stale-while-revalidate) for APIs.
  • [ ] Keep private/user data no-store and fetch on the client if possible.
  • [ ] Validate with curl -I and your CDN cache inspector.

10) TL;DR patterns I keep reusing

  • Marketing pages: force-static + revalidate: 3600.
  • Blog: ISR via revalidate, plus revalidateTag('posts') on publish.
  • Dashboards: static shell, client data with SWR; server APIs use s-maxage.
  • Search: dynamic route, no-store fetch, server actions for mutations.

Caching is where Next.js quietly shines. Master these knobs and your app feels instant without hand-rolling complexity.

If you want the same guide as a printable cheat-sheet, say the word—I’ll share a PDF.


Tags

nextjs react performance caching webdev tutorial

Top comments (0)