On-Demand ISR 按需增量静态再生成:基于 Build Output API 的预构建部署实战指南
【免费下载链接】examplesEnjoy our curated collection of examples and solutions. Use these patterns to build your own robust and scalable applications.项目地址: https://gitcode.com/GitHub_Trending/examples1/examples
在 Vercel 的 Build Output API 生态中,预构建(Prebuilt)项目如何实现"内容更新时才重新生成页面"的按需增量静态再生成(On-Demand Incremental Static Regeneration,简称 On-Demand ISR)?本指南以仓库中 build-output-api/on-demand-isr 示例为核心,详细讲解 Prerender Function 的按需缓存失效机制、x-prerender-revalidate请求头的用法、bypassToken的配置与安全实践,并对照仓库内基于 Next.js 的 solutions/on-demand-isr 原生实现,帮助你同时掌握 Build Output API 与 Next.js 两种落地路径。
示例背景:Build Output API 与预构建部署
Build Output API 允许开发者绕过框架的构建流程,直接提供符合规范的构建产物(即"预构建"项目)。本仓库的 build-output-api 目录集中展示了这类项目的多种形态,包括 Edge Functions、Edge Middleware、Image Optimization、Prerender Functions、Overrides、Routes、Serverless Functions 与静态文件等,而 On-Demand ISR 示例 正是其中讲解"按需重新验证缓存"的一环。
在该示例中,当使用Prerender Functions时,你可能希望基于某个事件(如用户点击按钮、Webhook 回调、数据变更通知)按需(On-Demand)重新验证某个路径的缓存,而不是依赖固定的时间窗口轮询刷新。这正是 On-Demand ISR 的适用场景:事件驱动 → 精准失效 → 重新生成 → 写入缓存。
Demo 行为:如何验证缓存是否真的生效
示例提供了在线演示(README 中标明为 https://build-output-api-isr.vercel.sh),页面包含两个区块:HTML Content与API Content。两者实现的是同一效果,只是分别作用于 HTML 响应与 API 响应两种不同的响应类型。
验证缓存是否工作的方式非常直观:
- 多次刷新页面,观察页面上的Server Time与API Result两个值——在刷新之间保持一致,说明缓存正在生效;
- 点击任一Revalidate链接,触发按需重新验证,对应的值随即更新;
- 更新之后再次多次刷新,会发现该值只更新了一次,随后又进入缓存状态。
这一行为精确对应 On-Demand ISR 的核心语义:平时读取缓存,仅在显式触发时重建内容,重建结果立即被缓存供后续请求复用。
工作原理:x-prerender-revalidate 请求头与 bypassToken
On-Demand ISR 在 Build Output API 中的底层机制并不复杂,核心是一条请求头约定:
要对某个 Prerender Function 的路径执行重新验证,只需向该路径发起一个携带
x-prerender-revalidate: <bypassToken>请求头的GET或HEAD请求。其中的<bypassToken>必须与该 Prerender Function 对应的<name>.prerender-config.json文件中的值一致。
也就是说,构建产物目录中每个 Prerender Function 都会伴随一个.prerender-config.json配置文件,其中记录了该函数的绕过令牌(bypassToken)。当带正确令牌的请求到达时,缓存会被重新验证,绕开 Vercel 默认提供的缓存层,直接执行函数逻辑并生成新内容。
需要说明的是,.vercel/output/functions/下的这些构建产物(如index.prerender-config.json、index.func/、revalidate.func/等)是在构建阶段生成的输出文件,并非仓库中静态维护的源码;在阅读源码时,应以 README.md 描述的文件名与行号为参照,在实际部署后的构建产物中核对。
示例中的路径与触发链路
示例演示了三条路径的按需重新验证,其中/与/data的链路完全相同,只是响应类型不同:
| 路径 | 对应产物 | 说明 |
|---|---|---|
/ | index.prerender-config.json(bypassToken)、index.func/index.js(自定义重新验证处理器)、revalidate.func/index.js(实际触发重新验证) | HTML 页面内容的按需重新验证 |
/data | data.prerender-config.json(bypassToken)、index.func/index.js(自定义重新验证处理器)、revalidate.func/index.js(实际触发重新验证) | API 响应内容的按需重新验证 |
触发链路可以概括为:
用户点击 Revalidate / Webhook 调用 │ ▼ 自定义重新验证处理器(index.func/index.js) │ ▼ 携带 x-prerender-revalidate: <bypassToken> 的 GET/HEAD 请求 │ ▼ revalidate.func(实际触发重新验证)→ 绕过缓存 → 重新生成并缓存按需生成新路径:博客示例
除了对已有路径做"重新验证",On-Demand ISR 还支持按需生成全新的路径。示例中附带了一个博客应用:
/blog/[slug]:基础博客预渲染模板,允许通过 On-Demand ISR 按需生成新路径,同样受index.prerender-config.json中的bypassToken保护;/blog/post-1、/blog/post-2:两个初始预渲染的博客文章,作为站点发布时的种子内容。
这一模式非常适合内容量巨大的场景:不必在构建时一次性生成全部页面,而是先预渲染少量入口路径,待某个 slug 被触发访问(或收到 CMS Webhook)时,再按需生成并缓存该路径。对内容型站点而言,这兼顾了静态页面的性能与动态内容的及时性。
安全实践:bypassToken 与 authToken
任何"允许外部触发重新验证"的能力都必须谨慎保护,README 给出了两条明确的安全指引:
bypassToken
- 示例中所有路径复用了同一个静态
bypassToken值,仅用于演示; - 生产环境中,
bypassToken应当是在构建时生成的随机字符串; - 该字符串不应暴露给用户或客户端,除非在已认证的场景下才可传递。
由于x-prerender-revalidate的校验就是比对请求头中的令牌与.prerender-config.json中的值,令牌一旦泄露,攻击者即可随意触发全站缓存重建,造成资源消耗甚至内容被篡改的风险,因此随机化、构建时生成、不可客户端可见是三条底线。
authToken
- 示例中的
authToken同样仅用于演示; - 生产系统中,对任何能够触发重新验证的入口,都应采用行业最佳实践加以保护,防止恶意滥用。
换句话说,即使 bypassToken 保证了"请求必须携带令牌",你也需要在自己的业务层(如 API 路由、Serverless Function)用authToken或其他鉴权机制限制"谁有资格发起这次携带令牌的请求",形成纵深防御。
对照实现:Next.js 原生 On-Demand ISR(仓库源码佐证)
Build Output API 是底层协议,而日常开发中更多直接使用 Next.js 的框架级 API。仓库中的 solutions/on-demand-isr 提供了完整的 Next.js 对照实现,二者解决的问题完全相同,理解它可以反过来加深对 Build Output API 机制的认识。
页面端:getStaticProps + 固定时间窗口 ISR
在 pages/index.tsx 中,页面通过getStaticProps在构建期抓取数据,并配置了固定时间的revalidate:
export const getStaticProps: GetStaticProps = async () => { const products = await api.list() return { props: { products, date: new Date().toTimeString(), }, } }该示例页面对应的标准 ISR 形态(含revalidate: 10时间窗口)在页面代码的说明片段中也有展示:
// This function gets called at build time on server-side. // It may be called again, on a serverless function, if // revalidation is enabled and a new request comes in export async function getStaticProps() { const res = await fetch('https://.../posts') const posts = await res.json() return { props: { posts, }, // Next.js will attempt to re-generate the page: // - When a request comes in // - At most once every 10 seconds revalidate: 10, // In seconds } }时间窗口 ISR 的局限在于:即使数据没有变化,页面也可能周期性重建;而"数据变化才重建"正是 On-Demand ISR 的改进点——自 Next.js 12.2.0 起,可以在 API Routes 中使用res.revalidate实现按需触发。
触发端:res.revalidate 按需重新验证
仓库中真正实现按需触发的 API 路由在 pages/api/revalidate/index.ts,代码极简:
import type { NextApiRequest, NextApiResponse } from 'next' export default async function handler( _req: NextApiRequest, res: NextApiResponse ) { await res.revalidate('/') return res.json({ revalidated: true }) }调用该 API 路由即可按需重新验证/路径:不仅会清除旧内容,还会同步重新执行getStaticProps、生成新内容并缓存给下一个用户。这样页面上的revalidate时间窗口甚至可以逐步增大甚至移除,把刷新时机完全交给事件(电商库存变更、CMS 内容更新、Webhook、机器人推送等)。
批量路径与"即发即忘"模式
当需要重新验证多个路径时,需要为每个路径各调用一次revalidate。仓库页面代码给出了两种策略。
并行等待所有路径完成(适合要求本次触发后全部就绪的场景):
export default async function handler(_req, res) { // Get paths to revalidate const paths = await api.pathsToRevalidate() // Revalidate every path await Promise.all(paths.map(res.revalidate)) // Return a response to confirm everything went ok return res.json({ revalidated: true }) }即发即忘(fire and forget)(适合不关心结果、追求快速响应的场景):
export default async function handler(_req, res) { // Get paths to revalidate const paths = await api.pathsToRevalidate() // Revalidate every path without awaiting paths.forEach(res.revalidate) // Return a response to confirm everything went ok return res.json({ revalidated: true }) }选择哪种模式需要考虑函数执行成本:重新验证某个路径会运行该路径对应的getStaticPropsserverless 函数,计入函数执行时间;而在 API 路由中await每个路径的重新验证,也会让 API 路由本身运行更久,同样计入函数执行时间。因此对于大量路径,如果业务上允许异步完成,推荐使用"即发即忘"策略以控制成本。
安全提示在两种实现中的一致性
无论是 Build Output API 还是 Next.js 原生实现,README 都反复强调同一件事:暴露重新验证端点时要格外小心,防止 DDoS 攻击。页面 pages/index.tsx 明确建议通过请求 key、token 等方式保护端点,这与 Build Output API 示例中的bypassToken、authToken双令牌设计一脉相承。
如何运行与部署
预构建示例(Build Output API)
该示例属于build-output-api目录下的预构建项目。按目录级 README 的说明,在对应的示例目录内执行以下命令即可部署:
vercel deploy --prebuiltNext.js 原生示例(solutions/on-demand-isr)
仓库中的 solutions/on-demand-isr/README.md 提供了另一种使用方式:使用create-next-app引导项目,并指定示例仓库地址(此处为当前仓库中对应的解决方案目录):
pnpm create next-app --example https://github.com/vercel/examples/tree/main/solutions/on-demand-isr随后启动开发服务器:
pnpm dev应用默认运行在 http://localhost:3000。本地验证流程为:访问首页记录当前 Server Time → 多次刷新确认不变 → 点击页面上的 Revalidate 按钮(或直接访问/api/revalidate端点)→ 刷新观察时间更新 → 再次多次刷新确认重新进入缓存状态。
小结
通过本指南你可以掌握两条路径下的 On-Demand ISR:
- Build Output API 层:理解了 Prerender Function 的缓存按需失效机制,即通过携带
x-prerender-revalidate: <bypassToken>请求头的GET/HEAD请求触发重新验证,bypassToken存放在对应的.prerender-config.json中,且支持按需生成全新的动态路径; - Next.js 框架层:通过仓库源码确认了
res.revalidate('/')的同步重新生成语义、Promise.all并行批量与forEach即发即忘两种批量策略,以及函数执行时间成本上的取舍。
两者共享同一条安全底线:触发重新验证的入口必须用随机化的构建期令牌与业务层鉴权双重保护,避免被恶意滥用。把握住"事件驱动、按需重建、缓存复用"这一核心思想,你就可以把静态生成的性能优势与动态内容更新的及时性同时收入囊中。
【免费下载链接】examplesEnjoy our curated collection of examples and solutions. Use these patterns to build your own robust and scalable applications.项目地址: https://gitcode.com/GitHub_Trending/examples1/examples
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考