Templates / Next.js revalidate
Revalidating Next.js pages on a schedule
Time-based revalidation rebuilds on the next request after the window lapses, which means a visitor waits for the build. Calling the revalidate route on a schedule keeps pages fresh without that.
The recipe
- Request
- POST https://{{domain}}/api/revalidate?secret={{secret}}&path=/
- Schedule
- Every hour (0 * * * *)
- Timeout
- 30s
- Attempts
- 3
- Counts as success
- 2xx
You supply
- Your domain
- example.com
Host only — no https:// and no trailing slash.
- Shared secret
- s3cr3t-valuestored encrypted
Whatever your endpoint checks the caller against.
Why this job exists
Incremental Static Regeneration is lazy by design. Setting revalidate: 3600 does not rebuild the page hourly; it marks the cached page stale after an hour, and the next request triggers a regeneration. With stale-while-revalidate that visitor gets the old page and the rebuild happens behind them — better than blocking, but they still saw stale content, and someone has to be first.
On a low-traffic page the effect compounds: pages can sit stale for a long time because nobody is arriving to trigger the rebuild, and then the one person who does arrive sees the oldest possible version.
On-demand revalidation solves it from the other direction. Next.js exposes revalidatePath and revalidateTag, and a route that calls them can be invoked by anything — including a scheduler, which turns lazy revalidation into an active refresh that happens before a reader arrives.
Setting it up
Add a route that checks a secret and revalidates the paths you name. Guard it: an unauthenticated revalidation endpoint lets anyone force your site to rebuild on demand, which is a cheap way to run up your bill:
app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';
export async function POST(request: Request) {
const { searchParams } = new URL(request.url);
if (searchParams.get('secret') !== process.env.REVALIDATE_SECRET) {
return Response.json({ error: 'Invalid secret' }, { status: 401 });
}
const path = searchParams.get('path') ?? '/';
revalidatePath(path);
return Response.json({ revalidated: true, path, now: Date.now() });
}The recipe passes the secret and the path as query parameters, matching the route above. For a content site, revalidateTag is usually the better primitive — tag your fetches by content type and refresh a whole class of pages in one call.
Checking it actually works
- Check the response body says revalidated: true rather than merely returning 200.
- Change something upstream, wait one cadence, and load the page without a hard refresh to confirm it updated.
- Watch your platform’s function invocation count — revalidation is a build, and building every page every hour has a cost worth seeing before it appears on an invoice.
Where it goes wrong
An unguarded revalidate route
Without the secret check, anyone who finds the path can force rebuilds as fast as they can make requests. Every one of those is billable compute on most platforms.
Revalidating everything
Calling revalidatePath with the layout type invalidates the entire tree. On a large site that is a full rebuild on a schedule. Name the paths that actually change, or use tags.
Assuming it is a substitute for webhooks
If your CMS can call a webhook on publish, that is strictly better — the page refreshes when the content changes rather than up to an hour later. Use the schedule for data that changes without an event, such as a feed or an aggregate.
Questions
Why not just lower the revalidate value?
Because it does not do what it sounds like. A lower value marks pages stale sooner but still waits for a request to rebuild them, so on a quiet page you get more staleness, not less.
Does this work on the pages router?
Yes — use res.revalidate(path) in an API route instead of revalidatePath. The scheduling side is identical.
Is this the same as cache warming?
Related but distinct. Revalidation rebuilds the page from source data; warming populates a cache with whatever the page currently renders. Sites that need both usually revalidate first and warm afterwards.
Running it here
This is a preset in the product, not an illustration. Pick it in the dashboard, fill in the 2 values above, and the job is created, scheduled and enabled — with retries, a record of every run showing status, latency and response, and an alert the first time one fails. Secret values are encrypted at rest and never rendered back.
The free plan runs 3 jobs hourly and needs no card. Where a recipe wants a finer cadence than your plan allows, it is slowed to the fastest schedule you are permitted rather than rejected.