Next.js增量静态再生ISR #

一、ISR基础 #

1.1 什么是ISR #

增量静态再生允许在构建后更新静态页面:

text
构建时生成 → 定期更新 → 保持静态优势

1.2 ISR优点 #

  • 静态页面速度
  • 数据保持更新
  • 服务器压力小
  • SEO友好

1.3 ISR缺点 #

  • 数据有延迟
  • 首次更新较慢

1.4 适用场景 #

  • 博客文章
  • 产品页面
  • 新闻列表
  • 分类目录

二、基于时间的重新验证 #

2.1 页面级配置 #

tsx
export const revalidate = 60

export default async function Page() {
    const data = await fetch('https://api.example.com/data')
    
    return <div>{data}</div>
}

2.2 fetch级配置 #

tsx
export default async function Page() {
    const data = await fetch('https://api.example.com/data', {
        next: { revalidate: 60 },
    })
    
    return <div>{data}</div>
}

2.3 工作原理 #

text
请求1 → 返回缓存页面 → 后台触发更新
请求2(60秒内)→ 返回缓存页面
请求3(60秒后)→ 返回新页面

2.4 时间选择 #

时间 适用场景
60秒 新闻、动态
3600秒 博客、产品
86400秒 静态内容

三、按需重新验证 #

3.1 基于标签 #

设置标签

tsx
export default async function Page() {
    const posts = await fetch('https://api.example.com/posts', {
        next: { tags: ['posts'] },
    })
    
    return <div>{posts}</div>
}

触发重新验证

tsx
'use server'

import { revalidateTag } from 'next/cache'

export async function createPost(data: PostData) {
    await db.post.create({ data })
    
    revalidateTag('posts')
}

3.2 基于路径 #

tsx
'use server'

import { revalidatePath } from 'next/cache'

export async function updatePost(slug: string, data: PostData) {
    await db.post.update({
        where: { slug },
        data,
    })
    
    revalidatePath('/blog')
    revalidatePath(`/blog/${slug}`)
}

3.3 API Route触发 #

tsx
import { revalidateTag, revalidatePath } from 'next/cache'
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
    const body = await request.json()
    
    if (body.tag) {
        revalidateTag(body.tag)
    }
    
    if (body.path) {
        revalidatePath(body.path)
    }
    
    return NextResponse.json({ revalidated: true })
}

3.4 Webhook触发 #

tsx
import { revalidateTag } from 'next/cache'
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
    const body = await request.json()
    
    if (body.model === 'post') {
        revalidateTag('posts')
    }
    
    if (body.model === 'product') {
        revalidateTag('products')
    }
    
    return NextResponse.json({ received: true })
}

四、缓存标签管理 #

4.1 标签组织 #

tsx
const CACHE_TAGS = {
    posts: 'posts',
    post: (slug: string) => `post:${slug}`,
    products: 'products',
    product: (id: string) => `product:${id}`,
    categories: 'categories',
} as const

export default async function Page() {
    const posts = await fetch('/api/posts', {
        next: { tags: [CACHE_TAGS.posts] },
    })
    
    return <div>{posts}</div>
}

4.2 多标签 #

tsx
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
    const { slug } = await params
    const post = await fetch(`/api/posts/${slug}`, {
        next: {
            tags: [CACHE_TAGS.posts, CACHE_TAGS.post(slug)],
        },
    })
    
    return <article>{post}</article>
}

4.3 精确更新 #

tsx
'use server'

import { revalidateTag } from 'next/cache'

export async function updatePost(slug: string, data: PostData) {
    await db.post.update({
        where: { slug },
        data,
    })
    
    revalidateTag(`post:${slug}`)
}

五、ISR与动态路由 #

5.1 generateStaticParams #

tsx
export const revalidate = 3600

export async function generateStaticParams() {
    const posts = await getPosts()
    
    return posts.map(post => ({
        slug: post.slug,
    }))
}

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
    const { slug } = await params
    const post = await getPost(slug)
    
    return <article>{post.content}</article>
}

5.2 动态参数 #

tsx
export const dynamicParams = true
export const revalidate = 3600

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
    const { slug } = await params
    const post = await getPost(slug)
    
    return <article>{post.content}</article>
}

六、ISR最佳实践 #

6.1 分层缓存 #

tsx
export default async function Page() {
    const staticContent = await fetch('/api/static', {
        cache: 'force-cache',
    })
    
    const semiStaticContent = await fetch('/api/semi-static', {
        next: { revalidate: 3600 },
    })
    
    const dynamicContent = await fetch('/api/dynamic', {
        cache: 'no-store',
    })
    
    return (
        <div>
            <StaticSection data={staticContent} />
            <SemiStaticSection data={semiStaticContent} />
            <DynamicSection data={dynamicContent} />
        </div>
    )
}

6.2 条件重新验证 #

tsx
'use server'

import { revalidatePath } from 'next/cache'

export async function publishPost(slug: string) {
    const post = await db.post.update({
        where: { slug },
        data: { published: true },
    })
    
    if (post.published) {
        revalidatePath('/blog')
        revalidatePath(`/blog/${slug}`)
    }
}

6.3 批量更新 #

tsx
'use server'

import { revalidateTag } from 'next/cache'

export async function bulkUpdatePosts(posts: PostData[]) {
    await db.post.createMany({ data: posts })
    
    revalidateTag('posts')
}

七、ISR调试 #

7.1 缓存头 #

查看响应头:

text
x-nextjs-cache: HIT    # 缓存命中
x-nextjs-cache: MISS   # 缓存未命中
x-nextjs-cache: STALE  # 缓存过期

7.2 开发模式 #

开发模式下ISR默认禁用,可以通过以下方式测试:

tsx
export default async function Page() {
    console.log('Page rendered at:', new Date().toISOString())
    
    const data = await fetch('/api/data', {
        next: { revalidate: 60 },
    })
    
    return <div>{data}</div>
}

7.3 预览模式 #

tsx
import { draftMode } from 'next/headers'

export default async function Page() {
    const { isEnabled } = await draftMode()
    
    const data = await fetch('/api/data', {
        cache: isEnabled ? 'no-store' : 'force-cache',
    })
    
    return <div>{data}</div>
}

八、实战案例 #

8.1 博客系统 #

tsx
export const revalidate = 3600

export async function generateStaticParams() {
    const posts = await getPosts()
    return posts.map(post => ({ slug: post.slug }))
}

export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
    const { slug } = await params
    const post = await getPost(slug)
    
    return (
        <article>
            <h1>{post.title}</h1>
            <div>{post.content}</div>
        </article>
    )
}

8.2 电商产品 #

tsx
export const revalidate = 300

export async function generateStaticParams() {
    const products = await getProducts()
    return products.map(product => ({
        category: product.category,
        id: product.id,
    }))
}

export default async function ProductPage({ params }: { params: Promise<{ category: string; id: string }> }) {
    const { category, id } = await params
    const product = await getProduct(category, id)
    
    return (
        <div>
            <h1>{product.name}</h1>
            <p>价格: ¥{product.price}</p>
        </div>
    )
}

九、总结 #

ISR要点:

要点 说明
时间验证 revalidate配置
标签验证 revalidateTag
路径验证 revalidatePath
缓存标签 tags配置
动态路由 generateStaticParams

下一步,让我们学习客户端渲染!

最后更新:2026-03-28