Caching and Revalidation
Next.js remembers (caches) fetched data so pages are fast — and gives you exact controls to refresh that data the moment it changes.
What you will learn
- Explain why Next.js caches fetched data
- Refresh on a timer with time-based revalidate
- Refresh on demand with revalidatePath and revalidateTag
Why cache at all?
A cache is a saved copy of a result, kept so you do not have to do the slow work again. If ten thousand people open the same blog post, fetching its data from the database ten thousand times is wasteful — the post did not change. Next.js caches the result of a fetch by default, so after the first request the saved copy is reused. Pages stay fast and your database stays calm.
But a cache has a catch: if the real data changes, the saved copy can become stale (out of date). The whole art of caching is deciding when to refresh. Next.js gives you three precise ways.
Way 1 — refresh on a timer (time-based)
Tell a fetch to consider its cached copy fresh for a number of seconds with the next: { revalidate } option. After that time, the next visitor triggers a quiet refresh in the background.
// Re-fetch this data at most once every 60 seconds
const res = await fetch('https://example.com/api/prices', {
next: { revalidate: 60 },
});
const prices = await res.json();Note: Output (behaviour, not text): For 60 seconds, every visitor gets the cached prices instantly. After 60 seconds, the next visit triggers Next.js to fetch fresh prices in the background and update the cache. You get speed AND data that is never more than a minute old.
Way 2 — refresh a page on demand (revalidatePath)
A timer is fine for prices, but for something like "I just added a blog post, show it now", you do not want to wait 60 seconds. After you change data (usually in a Server Action), call revalidatePath to tell Next.js: "the cached version of this page is stale — rebuild it." (Recall a Server Action from the earlier lesson: a use server function a form calls to change data.)
// app/actions.js
'use server';
import { revalidatePath } from 'next/cache';
export async function addPost(formData) {
const title = formData.get('title');
// ...save the new post to the database...
revalidatePath('/blog'); // the blog list is now stale — refresh it
}Note: Output (behaviour): The new post is saved, then revalidatePath("/blog") clears the cached /blog page. The very next time anyone opens /blog, Next.js rebuilds it with the new post included — no 60-second wait, no full redeploy.
Way 3 — refresh by label (revalidateTag)
Sometimes the same data appears on several pages — a product shows on /products, /products/5 and the home page. Refreshing each path by hand is error-prone. Instead, tag the fetch with a label, then later refresh everything with that tag in one call.
// Tag the fetch with a label
const res = await fetch('https://example.com/api/products', {
next: { tags: ['products'] },
});
// Later, in a Server Action after editing a product:
import { revalidateTag } from 'next/cache';
revalidateTag('products'); // refreshes every fetch tagged 'products'Note: Output (behaviour): Every page that fetched data tagged "products" is marked stale in a single call. The next visit to any of those pages rebuilds with fresh product data. One label, many pages refreshed together.
A worked example: a shop
Imagine a shop where an admin edits a product price. Here is the full flow, step by step:
- The product list page fetches products with
next: { tags: ['products'] }— so the data is cached and labelled "products". - Thousands of shoppers load the page; they all get the fast cached copy. The database is barely touched.
- The admin opens an edit form and changes a price. The form calls a Server Action.
- Inside the action, after saving the new price, you call
revalidateTag('products'). - Next.js marks every "products" fetch stale. The next shopper to load the list gets a freshly rebuilt page with the new price — instantly and automatically.
| Tool | Refreshes | Best for |
|---|---|---|
next: { revalidate: N } | After N seconds | Data that changes on its own (prices, news) |
revalidatePath(path) | One specific page | After a known edit to a known page |
revalidateTag(tag) | All fetches with that tag | Shared data shown across many pages |
Tip: A reliable habit: every time a Server Action changes data, revalidate something straight after. Forgetting to revalidate is the #1 reason a Next.js app "saves but the screen does not update".
Watch out: Caching behaviour can differ between Next.js versions and is the most confusing topic for beginners — when something looks stale, your first question should always be "did I revalidate after the change?" If the data must be live on every request, opt out of caching for that fetch with { cache: 'no-store' }.
Q. You add a new blog post in a Server Action and want it to appear on the cached /blog list immediately. What do you call?
✍️ Practice
- Add
next: { revalidate: 30 }to a fetch and confirm the data only changes at most every 30 seconds. - In a Server Action, call
revalidatePathafter a (pretend) save and confirm the list page updates on the next visit.
🏠 Homework
- Take your blog: tag the posts fetch with
tags: ["posts"], then callrevalidateTag("posts")inside an "add post" Server Action so new posts appear immediately.