Next.js ISR 缓存机制与最佳实践教程

📌 适用背景

正在开发一个网站,SEO 管理系统或,页面需要缓存但又不能太久失效。目标:

  • 静态页面自动过期更新(避免旧数据)
  • 加载速度快(靠缓存)
  • 提供后门 /api/revalidate 接口用于立即刷新

1️⃣ ISR 的基础概念

Next.js 的 增量静态再生成(ISR) 允许页面:

  • 构建后第一次请求时生成(静态化)
  • 被缓存下来(在内存中,或磁盘中)
  • 按设定时间 revalidate 过期并再生成

2️⃣ 缓存机制详解

✅ 1. 页面级别 revalidate

设置页面过期时间方式:

plain 复制代码
// page.tsx 或 layout.tsx 中
export const revalidate = 3600 // 1小时自动过期

或在 fetch() 时设置:

plain 复制代码
await fetch(url, {
  next: { revalidate: 3600 }, // 设置数据缓存时间
})

✅ 2. 缓存类型对比

缓存类型 存储位置 速度 是否持久化 是否受 revalidate 控制
内存缓存 服务器内存 极快 ❌(重启丢失) ❌(时间不控制,仅清除后消失)
磁盘缓存 文件系统 ✅(按 revalidate 过期)

3️⃣ 配置方式对比

✅ 默认配置(含内存缓存

plain 复制代码
// next.config.ts
export default {
  // 不设置 isrMemoryCacheSize,默认 50MB
}
plain 复制代码
// page.tsx
export const revalidate = 3600

流程如下:

  1. 用户首次访问 → 页面生成,缓存到 内存和磁盘
  2. 后续访问(1小时内)→ 命中内存缓存(速度最快)
  3. 超过 1 小时 → 磁盘缓存过期,触发再生成
  4. 内存缓存仍可命中(直到内存满或重启丢失)

🚫 禁用内存缓存(推荐用于可控缓存策略

plain 复制代码
// next.config.ts
export default {
  experimental: {
    isrMemoryCacheSize: 0, // 关闭内存缓存
  },
}
plain 复制代码
// page.tsx
export const revalidate = 3600

流程如下:

  1. 页面只缓存到 磁盘
  2. 访问时从磁盘读取
  3. 磁盘缓存 1 小时后过期 → 触发再生成

适合场景:使用 Redis、Supabase 等外部缓存层或需要缓存可控性强的系统


4️⃣ 实战应用:SEO 页面配置示例

✅ 页面级 revalidate 配置

plain 复制代码
// app/[locale]/services/[slug]/page.tsx
export const revalidate = 3600 // 页面 1 小时后自动过期

export async function generateMetadata({ params }) {
  const { locale } = params
  const path = `/services/${params.slug}`

  const seoData = await getSeoByPath(path, locale)
  return {
    title: seoData?.title ?? "默认标题",
    description: seoData?.description ?? "",
  }
}

fetch 配置缓存时间

plain 复制代码
// lib/seo.ts
export async function getSeoByPath(path: string, locale: string = 'en') {
  const res = await fetch(
    `${process.env.API_BASE_URL}/v1/sys/seo-config/path?path=${encodeURIComponent(path)}&locale=${locale}`,
    {
      headers: { 'Content-Type': 'application/json' },
      next: { revalidate: 3600 }, // 1小时缓存
    }
  )

  return res.ok ? res.json() : null
}

5️⃣ 增加 /api/revalidate 接口(可选)

用于手动触发页面再生成,比如管理员更新了 SEO 配置。

plain 复制代码
// app/api/revalidate/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(req: NextRequest) {
  const secret = req.nextUrl.searchParams.get('secret')
  const path = req.nextUrl.searchParams.get('path')

  if (secret !== process.env.REVALIDATE_SECRET || !path) {
    return NextResponse.json({ error: 'Invalid request' }, { status: 401 })
  }

  try {
    await fetch(`${process.env.NEXT_PUBLIC_SITE_URL}${path}`, {
      headers: {
        'x-prerender-revalidate': '1', // 可选,仅标记用途
      },
    })

    return NextResponse.json({ revalidated: true, path })
  } catch (err) {
    return NextResponse.json({ error: 'Revalidate failed' }, { status: 500 })
  }
}

⚠️ 部署时注意保护接口安全,可使用 token 或 IP 限制。


6️⃣ 最佳实践总结

目标 推荐配置说明
🚀 兼顾性能与实时性 使用默认配置,保留内存缓存,页面设置 revalidate
📡 高实时性(强一致性) 禁用内存缓存 + 设置较短 revalidate
🔄 后台更新时立即刷新页面 提供 /api/revalidate 接口
🧠 多租户/自定义缓存系统 isrMemoryCacheSize: 0 禁用内存缓存,结合 Redis 等方案

✅ 推荐配置示例(SEO 系统)

plain 复制代码
// next.config.ts
import createNextIntlPlugin from "next-intl/plugin"

export default createNextIntlPlugin()({
  reactStrictMode: true,
  output: 'standalone',
  transpilePackages: ['@my-monorepo/ui'],
  images: {
    domains: ['0.assets.sunionfab.com', 'ufc-oversea.oss-eu-central-1.aliyuncs.com'],
  },
  experimental: {
    isrMemoryCacheSize: 0, // 禁用内存缓存
  },
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack'],
    })
    return config
  },
})

🧩 总结一句话:

设置 revalidate 控制页面过期时间,是否启用 isrMemoryCacheSize 取决于你对缓存性能与一致性的需求。