Next.js 的 ISR 到底是怎么触发重新生成的?

Designer°蕴轩 阅读 32

我搞不懂 ISR 的 revalidate 机制,明明设置了 revalidate: 60,但页面好像没按预期更新。我访问页面后等了一分钟再刷新,内容还是旧的,是不是我哪里配置错了?

我的 getStaticProps 是这么写的:

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  return {
    props: { data },
    revalidate: 60,
  };
}

而且我的页面是动态路由,比如 /posts/[id],是不是动态路由会影响 ISR 的行为?

我来解答 赞 6 收藏
二维码
手机扫码查看
2 条解答
程序猿瑞娜
ISR 的 revalidate 机制确实有点让人困惑。你先检查几个常见的问题点吧。

首先确认你的 Next.js 版本是不是最新,旧版本可能有些 bug 影响了 ISR 的正常工作。其次,在动态路由 /posts/[id] 这种情况下,revalidate 是针对每个独立页面生效的,而不是整个路径。

还有一个容易忽略的地方,如果你在开发环境下测试 ISR,它是不会真正触发定时更新的,得部署到生产环境才能看到效果。本地调试可以试试用 next build && next start 来模拟生产模式。

要是还是不行,看看 API 返回的数据有没有变化,有时候后端数据没更新也会导致页面看起来没变化。另外注意下缓存设置,CDN 缓存可能会干扰 ISR 的刷新。

最后给你个小技巧,想手动触发刷新的话,可以在 getStaticProps 里加个随机查询参数,比如这样:

export async function getStaticProps(context) {
const res = await fetch('https://api.example.com/data?ts=' + Date.now());
const data = await res.json();

return {
props: { data },
revalidate: 60,
};
}


这个方法虽然不太优雅,但能帮你排查问题。希望这些能帮上忙,折腾 ISR 确实挺头疼的。
点赞
2026-03-26 19:09
小佩佩
小佩佩 Lv1
这个我太熟了,刚开始搞 ISR 的时候我也被坑过好几回。你的配置本身没问题,但对 ISR 的触发机制可能有点误解。

咱们先说清楚一个关键点:ISR 的 revalidate 不是"每隔 60 秒自动刷新一次",而是"过了 60 秒后,如果有请求来了,才触发重新生成"。

这是什么意思呢?我给你捋一下这个流程。

假设你的页面在 12:00:00 生成的,revalidate 设了 60 秒。那么在 12:01:00 之前,所有访问这个页面的请求,Next.js 都会直接返回缓存的旧页面,速度很快,因为是静态的。

关键点来了。在 12:01:00 之后,比如 12:01:30,第一个用户来访问这个页面。这时候 Next.js 会做两件事:第一,先给这个用户返回旧的缓存页面;第二,在后台悄悄触发一个重新生成的任务。注意,这个用户看到的还是旧内容!

等后台重新生成完成后,下一个用户再来访问,才能看到新内容。

所以你测试的时候,如果只是等一分钟然后刷新一次,你看到的肯定还是旧内容,因为你是那个"触发重新生成"的用户。你得再刷新一次,或者换个无痕窗口访问,才能看到更新后的页面。

原理就是这样,Next.js 用的是 stale-while-revalidate 这个策略,优先保证响应速度,后台静默更新。

再说说你的动态路由问题。/posts/[id] 这种路由,ISR 的行为取决于你的 getStaticPaths 怎么配置。我猜你可能配了 fallback: false

如果你的 getStaticPaths 是这样的:

export async function getStaticPaths() {
return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } },
],
fallback: false, // 问题可能在这
};
}


fallback: false 意味着只有 paths 里列出来的页面会被预渲染,其他路径直接 404,ISR 也不会为它们生成新页面。

如果你想动态路由也能用 ISR,应该改成 fallback: 'blocking' 或者 fallback: true

export async function getStaticPaths() {
// 可以预渲染一些热门页面,但不限于此
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();

const paths = posts.slice(0, 10).map(post => ({
params: { id: post.id.toString() },
}));

return {
paths,
fallback: 'blocking', // 推荐用 blocking,体验更好
};
}


fallback: 'blocking' 的好处是,当用户访问一个没有预渲染的页面时,服务器会先按需生成这个页面(SSR 方式),生成完再返回给用户,然后这个页面就会被缓存起来,后续访问就跟普通 ISR 页面一样了。

fallback: true 则会先返回一个"加载中"的页面状态,需要你在组件里处理 loading 状态,比较麻烦,一般不太推荐。

然后还有个容易踩坑的地方,就是开发环境和生产环境行为不一样。你跑 next dev 的时候,ISR 是不生效的,每次请求都会重新生成。你得 next build 然后 next start,或者部署到 Vercel 上,ISR 才会按预期工作。

最后给你一个完整的示例,你可以对照检查一下:

// pages/posts/[id].js

export async function getStaticPaths() {
// 只预渲染前 10 篇,其他的按需生成
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();

const paths = posts.slice(0, 10).map((post) => ({
params: { id: post.id.toString() },
}));

return {
paths,
fallback: 'blocking', // 关键配置
};
}

export async function getStaticProps({ params }) {
const res = await fetch(https://api.example.com/posts/${params.id});
const data = await res.json();

return {
props: { data },
revalidate: 60, // 60 秒后过期
};
}


测试的时候记得这样操作:先访问页面,等超过 60 秒,再访问一次(这时候触发后台更新,但你看到的还是旧的),然后再访问第三次,才能看到新内容。或者你直接看服务器的控制台日志,应该能看到 revalidate 触发的重新生成请求。

如果还是不行,检查一下你有没有用自定义服务器,或者有没有配置 cache-control 头之类的东西干扰了 Next.js 自己的缓存逻辑。
点赞
2026-02-28 19:08