Next.js ISR 缓存机制与最佳实践教程
📌 适用背景
正在开发一个网站,SEO 管理系统或,页面需要缓存但又不能太久失效。目标:
- 静态页面自动过期更新(避免旧数据)
- 加载速度快(靠缓存)
- 提供后门
/api/revalidate
接口用于立即刷新
1️⃣ ISR 的基础概念
Next.js 的 增量静态再生成(ISR) 允许页面:
- 在构建后第一次请求时生成(静态化)
- 被缓存下来(在内存中,或磁盘中)
- 按设定时间
revalidate
过期并再生成
2️⃣ 缓存机制详解
✅ 1. 页面级别 revalidate
设置页面过期时间方式:
// page.tsx 或 layout.tsx 中
export const revalidate = 3600 // 1小时自动过期
或在 fetch()
时设置:
await fetch(url, {next: { revalidate: 3600 }, // 设置数据缓存时间
})
✅ 2. 缓存类型对比
缓存类型 | 存储位置 | 速度 | 是否持久化 | 是否受 revalidate 控制 |
---|---|---|---|---|
内存缓存 | 服务器内存 | 极快 | ❌(重启丢失) | ❌(时间不控制,仅清除后消失) |
磁盘缓存 | 文件系统 | 快 | ✅ | ✅(按 revalidate 过期) |
3️⃣ 配置方式对比
✅ 默认配置(含内存缓存)
// next.config.ts
export default {// 不设置 isrMemoryCacheSize,默认 50MB
}
// page.tsx
export const revalidate = 3600
流程如下:
- 用户首次访问 → 页面生成,缓存到 内存和磁盘
- 后续访问(1小时内)→ 命中内存缓存(速度最快)
- 超过 1 小时 → 磁盘缓存过期,触发再生成
- 内存缓存仍可命中(直到内存满或重启丢失)
🚫 禁用内存缓存(推荐用于可控缓存策略)
// next.config.ts
export default {experimental: {isrMemoryCacheSize: 0, // 关闭内存缓存},
}
// page.tsx
export const revalidate = 3600
流程如下:
- 页面只缓存到 磁盘
- 访问时从磁盘读取
- 磁盘缓存 1 小时后过期 → 触发再生成
适合场景:使用 Redis、Supabase 等外部缓存层或需要缓存可控性强的系统。
4️⃣ 实战应用:SEO 页面配置示例
✅ 页面级 revalidate 配置
// app/[locale]/services/[slug]/page.tsx
export const revalidate = 3600 // 页面 1 小时后自动过期export async function generateMetadata({ params }) {const { locale } = paramsconst path = `/services/${params.slug}`const seoData = await getSeoByPath(path, locale)return {title: seoData?.title ?? "默认标题",description: seoData?.description ?? "",}
}
✅ fetch
配置缓存时间
// 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 配置。
// 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 系统)
// 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
取决于你对缓存性能与一致性的需求。