
本文翻译自《 How Incremental Static Regeneration (ISR) Works in Next.js 》。
起因是笔者的博客是由Next.js搭建而成的,但上线之后遇到了一个问题:当笔者在后台新增或修改文章后,首页的文章列表一直没有更新,且需要重新打包部署之后才可以看到最新数据。于是,笔者找了不少资料最终解决了问题。在此过程中,今天翻译的这一篇文章最让我受益匪浅。
一个网站的页面根据响应请求方式的不同,可分为:静态页面、动态页面。
其中,静态页面在构建项目时生成,且,响应用户请求时,服务器直接返回该页面的html文件;
而,动态页面是 在用户请求时,由服务器获取到相关数据后即时生成html文件并返回给客户端。这个流程相对于静态页面来说会显得稍慢,但每次响应给客户端的数据都是最新的。
这两种响应方式各有利弊。静态页面的加载速度非常快,但如果需要响应新的数据,就必须重新构建、部署。动态页面虽然在用户请求时,始终会响应最新的内容,但需要更多的服务器资源去承接这部分的运算、响应工作(译者注:比如查询得到数据后生成html文件);
为了让两方的优势互补,ISR 应运而生,它让网站既拥有静态页面的响应速度,又拥有动态页面的高时效性。
接下去,我们将探讨什么是 ISR,以及,它的工作机制、最佳实践方案。
什么是ISR?
ISR 是 Next.js 中的一项重要功能,它允许你在构建、部署网站之后,在适当的时机下由服务器重新编译并生成最新的静态页面,以便让用户及时请求到最新内容的页面;
在过去,如果你构建、部署了一个静态网站,当你需要向用户呈现某一个网页修改后的内容时,就必须重新构建整个网站。这需要花费大量的时间成本,特别是对于大型网站而言。
ISR 很好的解决了这个问题。借助 ISR,你可以设置在特定的过期时间后 或 由你程序触发 让服务器在后台重新构建某一个页面。在这种技术方案下,用户不仅可以快速看到呈现的静态页面,而且在无需手动重新构建整站的情况下,由服务器后台自动重新构建某一个页面以便用户看到网页的最新内容。
简单来说,一方面,服务器可以向客户端快速响应静态页面文件;另一方面,服务器在设定的过期时间或在触发时机下,重新获取数据并生成静态页面。这样,用户始终可以快速的获得一个可靠的页面。
ISR的工作原理
要理解 ISR,首先我们要知道 Next.js 中构建页面的三种方式:
- Static Generation (SSG): 构建完毕之后,静态页面内容不会再改变。需要展示最新数据时,需重新构建整站页面;
- Server-Side Rendering (SSR): 每个请求过来时,都需要服务器获取到最新数据后生成页面给到客户端。这种方式的响应速度可能比较慢,毕竟每个请求来的时候,服务器都要进行运算、生成页面。
- Incremental Static Regeneration (ISR):对于过期的静态页面,才会按需在请求时重新构建页面。否则,服务器在接收到请求时,立即向用户返回已经构建好的静态页面。
ISR的工作流程:
当你使用 ISR 时,它工作流程依次是:
- 用户访问你的页面;
- 如果静态页面还在过期时间内,则 Next.js 会响应未过期的页面文件给客户端;
- 如果根据你设置的过期时间,静态页面已经过期的,则 Next.js 一方面会向客户端响应已经过期的页面文件,另一方面会在后台重新拉取数据并构建新页面;
- 等到新页面构建成功之后,下一个访问的用户会自动获取新版本的静态页面(译者注:在首次触发重新构建到构建完毕中间的时间段里,服务器依然会返回旧的页面)。
如果你需要为你的静态页面设置过期时间的,则可以通过在 getStaticProps 函数中使用 revalidate 参数来设置过期时间。
以下是最基础的ISR过期时间的设置方式(译者注:以下代码实现的业务场景应该是"为每个文章详情页生成静态页面的html文件"):
js
// pages/posts/[id].js
export async function getStaticProps(context) {
const { id } = context.params;
const post = await fetch(`https://example.com/posts/${id}`).then(res => res.json());
return {
props: {
post,
},
revalidate: 60, // Regenerate the page after 60 seconds
};
}
export async function getStaticPaths() {
const posts = await fetch('https://example.com/posts').then(res => res.json());
const paths = posts.map((post) => ({
params: { id: post.id.toString() },
}));
return { paths, fallback: 'blocking' };
}
以上代码的作用是:
- 该页面首次被访问时,会被构建并缓存(译者注:这里讲的应该是 getStaticPaths 函数的作用);
- 60秒后,当有人再次访问该页面时,Next.js 将在后台获取最新数据后重新构建页面(译者注:同时返回旧页面);
- 整个过程中,用户始终能第一时间获取到页面。
ISR触发页面重新构建的时机
既然你已经了解了什么是 ISR,接下来,就让我们来看看 ISR 是在何时、如何重新构建页面的吧。
ISR的具体流程如下:
- 用户请求一个静态页面;
- Next.js 检查是否存在已构建好的静态页面;
- 如果静态页面还未过期(在 revalidate 时间内),Next.js 就直接响应静态页面;
- 如果静态页面已经过期(超出 revalidate 时间),Next.js 在直接响应旧版的静态页面的同时,会在后台开始重新构建页面;
- 一旦重新构建完成,下一个用户就会看到最新构建的页面。
在这个流程中,有一点很重要:
用户无需等待即可快速获得一个静态页面,因为 ISR 在接收到请求后的第一时间响应页面,虽然这个页面可能是最新构建的页面,也可能是上一个版本的页面。
js
User visits page --> Is page fresh?
|
Yes | No
Serve cached page Serve cached page + Start background regeneration
|
Regeneration finished
|
Next user sees updated page
一个小例子:
假设你为某一个页面设置的过期时间(revalidate)为 30秒:
- 下午 12:00:00 → 页面构建并缓存;
- 下午 12:00:10 → 第一位用户访问,从缓存中提供未过期的页面;
- 下午 12:00:35 → 另一位用户访问,此时页面已经过期,因此 Next.js 会向用户提供旧页面的同时触发重新构建页面的操作;
- 下午 12:00:36 → 页面重新构建完成 & 最新页面准备就绪;
- 下午 12:00:40 → 下一位访问者获取到的是最新版的页面。
总之,在ISR技术加持下,该页面总能快速响应,并且,会在用户毫无察觉的情况下悄然更新。
常见的ISR使用场景
现在你肯定很想知道,究竟什么场景能使用 ISR 呢?
以下是几个ISR的绝佳应用场景:
1. 博客或新闻网站
如果你正在运营一个博客或新闻网站,那么肯定会经常添加新文章。并且,你肯定希望读者能看到最新的内容,同时,也更希望页面的加载速度足够快。
此时,如果你使用了ISR技术的话,就能实现:
- 文章会被构建成为静态页面;
- 当你发布新文章时,页面会在用户无感知的情况下被更新;
- 你的读者始终能快速看到页面内容;
举个例子:
一个科技博客网站,每隔几个小时就会更新一次内容,那么你就可以设置过期时间为一个小时(revalidate: 3600),这样页面就会每隔一个小时被重新构建一次了。
2. 电商页面
在在线商城网站中,诸如价格、库存、商品描述等产品信息会经常变动。作为站长,你肯定希望数据可以及时更新并且用户能快速看到网页内容。
此时,如果你使用了ISR技术的话,就能实现:
- 产品页面可以立即被加载;
- 如果有任何数据变动(比如促销活动),页面会在不影响购物体验的情况下悄然更新;
举个例子:
你可以为产品页面设置过期时间为5分钟(revalidate: 300),以便在不降低页面响应速度的情况下,尽快展示最新数据。
3. 仪表盘或用户相关数据
如果你的网站有仪表盘、评论、论坛帖子或用户资料,并且这些内容并非经常更新,那么在这个场景中使用ISR会是一个明智之选。
毕竟,如果你使用了ISR技术的话,就能实现:
- 及时展示更新后的帖子、评论或统计数据,同时也不会让服务器负载过重;
- 页面内容会根据你设定的过期时间来刷新;
举个例子:
一个热评网站可以设置过期时间为24小时(revalidate: 86400)来达到每天更新 "热门产品" 列表的目的。
综上几个案例,我们可以得出结论:如果你的页面偶尔发生变化(并非时刻在变)的,并且你希望实现高速度和内容时效性平衡的,那么请使用ISR。
ISR的最佳实践
为了让 ISR 获得最佳效果,你必须正确的使用它,以下是几个比较重要的建议,你可以参考看看:
1. 设置合适的过期时间(设置合适的revalidate参数)
首先,你需要慎重的思考一下你网站的内容实际多久更新一次:
- 如果你的内容每小时更新一次,那你可以设置revalidate: 3600(即 1 小时)。
- 如果你的内容每天更新一次,那你可以设置revalidate: 86400(即 24 小时)。
- 如果你的内容每隔几分钟更新一次,那你可以设置revalidate: 300(即 5 分钟)。
小提示:
请务必选择一个可以平衡"数据实时性与服务器负载"的过期时间,因为设置的过期时间越短,更新的频率越高、服务器压力越大。
2. 务必做好错误兜底方案
有时,在重新生成页面时,你的数据源(如 API)可能会出现故障。
为避免页面崩溃,请务必:
- 在getStaticProps函数中使用 try catch;
- 如果获取数据失败,可以给用户呈现 错误提示 或 给一个简单的错误页面;
代码示例如下:
js
export async function getStaticProps() {
try {
const data = await fetch('https://example.com/data').then(res => res.json());
return {
props: { data },
revalidate: 60,
};
} catch (error) {
console.error('Failed to fetch data:', error);
return {
props: { data: null },
revalidate: 60,
};
}
}
3. 为了SEO,咱也得考虑考虑ISR吖
由于 ISR 能快速响应静态页面,因此对搜索引擎优化(SEO)很有帮助。
但是请记住:
- 即使数据获取失败,也请返回有意义的内容。
- 使用 ISR 时,请避免显示 "加载中..." 状态,因为为了友好的SEO效果,页面在呈现给用户和搜索引擎时都应该保持页面及数据完整性。
ISR的潜在问题与应对方案
尽管 ISR 很出色,但如果使用不得当,仍会出现一些不必要的麻烦,以下是几个需要注意的地方:
1. 过期数据问题
由于ISR是在请求时去验证页面是否过期,如果过期的,则先返回旧版本的页面,再去启动后台程序重新构建页面的,所以当页面尚未重新构建完成时,请求过来的用户依然只能看到旧的页面数据。
如何应对?
- 这就需要站长对数据更新的时间有一个比较清醒的认识,同时设置合适的revalidate时间;
- 如果站点数据的时效性要求非常高的,请使用 SSR 而不是 ISR;
2. 部署服务器配置错误问题
ISR的技术落地是需要服务器支持才能正常进行的。如果你在 Vercel 或 Netlify 等平台上托管网站,它们会为你把相关的技术操作点处理掉。
但如果你使用的自定义服务器或不同的托管方式,请确保:
- 你的服务器必须能 运行无服务函数 或者 运行后端服务。
- 不要误将你的网站以静态托管的方式部署(比如没有任何后端的普通 S3 存储桶)。
小提示:
请务必详读云服务商的相关文档,以确保他们的服务能正确支持Next.js ISR。
3. 大规模重建可能导致负载峰值
如果你设置的过期(revalidate)时间过短,并且你有非常多的页面,服务器资源可能会被大量的重新构建进程占用。
如何应对?
- 务必明确网站数据的更新周期,并设置适当的过期时间;
- 对于大型网站,可以考虑使用按需增量静态再生(在这种模式下,你可以手动控制页面何时重建,我们接下来会讲到)。
高级应用:按需增量静态再生(On-Demand ISR)
通常情况下,使用 ISR 时,旧页面会在你定义的过期时间后被重新构建生成的新页面替代。
但,有时你希望能有一种机制来让你在某些关键时刻就去触发页面重新生成,比如:
- 一篇新博客文章发布后;
- 一个产品更新后;
- 一些用户数据更新后;
这时候,按需增量静态再生(On-Demand ISR)就发挥作用了。
通过On-Demand ISR,你可以在适当的时机下使用 API 路由手动触发页面重新构建,而无需等待定时器到期;
如何设置按需增量静态再生(On-Demand ISR)?
你只需简单两步:
- 一个触发 Next.js 重新构建页面的 API 路由;
- 一个用于保护你的 API 的密钥,这样就能确保只有你才能触发它;
On-Demand ISR的 API 路由 代码示例:
js
// pages/api/revalidate.js
export default async function handler(req, res) {
// Secret token check for security
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' });
}
try {
const pathToRevalidate = req.query.path;
await res.revalidate(pathToRevalidate);
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).json({ message: 'Error revalidating' });
}
}
如何触发它?
你可以像这样向你的 API 路由发出一个POST请求:
POST /api/revalidate?secret=YOUR_TOKEN&path=/your-page-path
例如:
POST /api/revalidate?secret=MY_SECRET_TOKEN&path=/posts/my-new-post
以上代码将触发Next.js立即重新构建 /posts/my-new-post页面,而无需等待定时器。
重要提示:
- 请务必使用并妥善存储你的密钥(例如可以存储在.env文件中);
- 确保只有受信任的系统(如您的内容管理系统或管理面板)才能调用该 API;
写在最后
ISR 是 Next.js 中最出色的功能之一,它同时赋予你静态页面快速响应能力 和 动态内容的较高时效性。
当你使用了ISR之后:
- 您的页面加载速度倍儿棒;
- 无需重新构建整个站点,也可将您网站的最新数据呈现给用户;
- 您的网站将给人流畅、现代且专业的感觉;
总之,如果应用ISR得当,你就可以快速构建出更快、更高时效的网站。