How to implement Incremental Static Regeneration (ISR)
Source URL: https://nextjs.org/docs/app/guides/incremental-static-regeneration
How to implement Incremental Static Regeneration (ISR)
Section titled “How to implement Incremental Static Regeneration (ISR)”Examples
Incremental Static Regeneration (ISR) enables you to:
- Update static content without rebuilding the entire site
- Reduce server load by serving prerendered, static pages for most requests
- Ensure proper
cache-controlheaders are automatically added to pages - Handle large amounts of content pages without long
next buildtimes
Here’s a minimal example:
interface Post { id: string title: string content: string}
// Next.js will invalidate the cache when a// request comes in, at most once every 60 seconds.export const revalidate = 60
export async function generateStaticParams() { const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) => res.json() ) return posts.map((post) => ({ id: String(post.id), }))}
export default async function Page({ params,}: { params: Promise<{ id: string }>}) { const { id } = await params const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then( (res) => res.json() ) return ( <main> <h1>{post.title}</h1> <p>{post.content}</p> </main> )}// Next.js will invalidate the cache when a// request comes in, at most once every 60 seconds.export const revalidate = 60
export async function generateStaticParams() { const posts = await fetch('https://api.vercel.app/blog').then((res) => res.json() ) return posts.map((post) => ({ id: String(post.id), }))}
export default async function Page({ params }) { const { id } = await params const post = await fetch(`https://api.vercel.app/blog/${id}`).then((res) => res.json() ) return ( <main> <h1>{post.title}</h1> <p>{post.content}</p> </main> )}Here’s how this example works:
- During
next build, all known blog posts are generated - All requests made to these pages (e.g.
/blog/1) are cached and instantaneous - After 60 seconds has passed, the next request will still return the cached (now stale) page
- The cache is invalidated and a new version of the page begins generating in the background
- Once generated successfully, the next request will return the updated page and cache it for subsequent requests
- If
/blog/26is requested, and it exists, the page will be generated on-demand. This behavior can be changed by using a different dynamicParams value. However, if the post does not exist, then 404 is returned.
Reference
Section titled “Reference”Route segment config
Section titled “Route segment config”Functions
Section titled “Functions”Examples
Section titled “Examples”Time-based revalidation
Section titled “Time-based revalidation”This fetches and displays a list of blog posts on /blog. After an hour has passed, the next visitor will still receive the cached (stale) version of the page immediately for a fast response. Simultaneously, Next.js triggers regeneration of a fresh version in the background. Once the new version is successfully generated, it replaces the cached version, and subsequent visitors will receive the updated content.
interface Post { id: string title: string content: string}
export const revalidate = 3600 // invalidate every hour
export default async function Page() { const data = await fetch('https://api.vercel.app/blog') const posts: Post[] = await data.json() return ( <main> <h1>Blog Posts</h1> <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> </main> )}export const revalidate = 3600 // invalidate every hour
export default async function Page() { const data = await fetch('https://api.vercel.app/blog') const posts = await data.json() return ( <main> <h1>Blog Posts</h1> <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> </main> )}We recommend setting a high revalidation time. For instance, 1 hour instead of 1 second. If you need more precision, consider using on-demand revalidation. If you need real-time data, consider switching to dynamic rendering.
On-demand revalidation with revalidatePath
Section titled “On-demand revalidation with revalidatePath”For a more precise method of revalidation, invalidate cached pages on-demand with the revalidatePath function.
For example, this Server Action would get called after adding a new post. Regardless of how you retrieve your data in your Server Component, either using fetch or connecting to a database, this will invalidate the cache for the entire route. The next request to that route will trigger regeneration and serve fresh data, which will then be cached for subsequent requests.
Note:
revalidatePathinvalidates the cache entries but regeneration happens on the next request. If you want to eagerly regenerate the cache entry immediately instead of waiting for the next request, you can use the Pages routerres.revalidatemethod. We’re working on adding new methods to provide eager regeneration capabilities for the App Router.
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() { // Invalidate the cache for the /posts route revalidatePath('/posts')}'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() { // Invalidate the cache for the /posts route revalidatePath('/posts')}View a demo and explore the source code.
On-demand revalidation with revalidateTag
Section titled “On-demand revalidation with revalidateTag”For most use cases, prefer revalidating entire paths. If you need more granular control, you can use the revalidateTag function. For example, you can tag individual fetch calls:
export default async function Page() { const data = await fetch('https://api.vercel.app/blog', { next: { tags: ['posts'] }, }) const posts = await data.json() // ...}export default async function Page() { const data = await fetch('https://api.vercel.app/blog', { next: { tags: ['posts'] }, }) const posts = await data.json() // ...}If you are using an ORM or connecting to a database, you can use unstable_cache:
import { unstable_cache } from 'next/cache'import { db, posts } from '@/lib/db'
const getCachedPosts = unstable_cache( async () => { return await db.select().from(posts) }, ['posts'], { revalidate: 3600, tags: ['posts'] })
export default async function Page() { const posts = getCachedPosts() // ...}import { unstable_cache } from 'next/cache'import { db, posts } from '@/lib/db'
const getCachedPosts = unstable_cache( async () => { return await db.select().from(posts) }, ['posts'], { revalidate: 3600, tags: ['posts'] })
export default async function Page() { const posts = getCachedPosts() // ...}You can then use revalidateTag in a Server Actions or Route Handler:
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() { // Invalidate all data tagged with 'posts' revalidateTag('posts')}'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() { // Invalidate all data tagged with 'posts' revalidateTag('posts')}Handling uncaught exceptions
Section titled “Handling uncaught exceptions”If an error is thrown while attempting to revalidate data, the last successfully generated data will continue to be served from the cache. On the next subsequent request, Next.js will retry revalidating the data. Learn more about error handling.
Customizing the cache location
Section titled “Customizing the cache location”You can configure the Next.js cache location if you want to persist cached pages and data to durable storage, or share the cache across multiple containers or instances of your Next.js application. Learn more.
Troubleshooting
Section titled “Troubleshooting”Debugging cached data in local development
Section titled “Debugging cached data in local development”If you are using the fetch API, you can add additional logging to understand which requests are cached or uncached. Learn more about the logging option.
module.exports = { logging: { fetches: { fullUrl: true, }, },}Verifying correct production behavior
Section titled “Verifying correct production behavior”To verify your pages are cached and revalidated correctly in production, you can test locally by running next build and then next start to run the production Next.js server.
This will allow you to test ISR behavior as it would work in a production environment. For further debugging, add the following environment variable to your .env file:
NEXT_PRIVATE_DEBUG_CACHE=1This will make the Next.js server console log ISR cache hits and misses. You can inspect the output to see which pages are generated during next build, as well as how pages are updated as paths are accessed on-demand.
Caveats
Section titled “Caveats”- ISR is only supported when using the Node.js runtime (default).
- ISR is not supported when creating a Static Export.
- If you have multiple
fetchrequests in a statically rendered route, and each has a differentrevalidatefrequency, the lowest time will be used for ISR. However, those revalidate frequencies will still be respected by the Data Cache. - If any of the
fetchrequests used on a route have arevalidatetime of0, or an explicitno-store, the route will be dynamically rendered. - Proxy won’t be executed for on-demand ISR requests, meaning any path rewrites or logic in Proxy will not be applied. Ensure you are revalidating the exact path. For example,
/post/1instead of a rewritten/post-1.
Platform Support
Section titled “Platform Support”| Deployment Option | Supported |
|---|---|
| Node.js server | Yes |
| Docker container | Yes |
| Static export | No |
| Adapters | Platform-specific |
Learn how to configure ISR when self-hosting Next.js.
Version history
Section titled “Version history”| Version | Changes |
|---|---|
v14.1.0 | Custom cacheHandler is stable. |
v13.0.0 | App Router is introduced. |
v12.2.0 | Pages Router: On-Demand ISR is stable |
v12.0.0 | Pages Router: Bot-aware ISR fallback added. |
v9.5.0 | Pages Router: Stable ISR introduced. |