introduction

next.js 允许你创建或者更新静态页面(在你构建了你的站点之后),

增量静态生成(ISR)能够让你基于每一页使用静态生成,无需重新构建整个站点,通过ISR,你能够保留静态的好处(当弹性缩放数百万页面时) .

为了使用ISR,增加revalidate属性到 getStaticProps函数中 ..

以下展示了一个静态更新博客内容的示例

  1. function Blog({ posts }) {
  2. return (
  3. <ul>
  4. {posts.map((post) => (
  5. <li key={post.id}>{post.title}</li>
  6. ))}
  7. </ul>
  8. )
  9. }
  10. // This function gets called at build time on server-side.
  11. // It may be called again, on a serverless function, if
  12. // revalidation is enabled and a new request comes in
  13. export async function getStaticProps() {
  14. const res = await fetch('https://.../posts')
  15. const posts = await res.json()
  16. return {
  17. props: {
  18. posts,
  19. },
  20. // Next.js will attempt to re-generate the page:
  21. // - When a request comes in
  22. // - At most once every 10 seconds
  23. revalidate: 10, // In seconds
  24. }
  25. }
  26. // This function gets called at build time on server-side.
  27. // It may be called again, on a serverless function, if
  28. // the path has not been generated.
  29. export async function getStaticPaths() {
  30. const res = await fetch('https://.../posts')
  31. const posts = await res.json()
  32. // Get the paths we want to pre-render based on posts
  33. const paths = posts.map((post) => ({
  34. params: { id: post.id },
  35. }))
  36. // We'll pre-render only these paths at build time.
  37. // { fallback: blocking } will server-render pages
  38. // on-demand if the path doesn't exist.
  39. return { paths, fallback: 'blocking' }
  40. }
  41. export default Blog

同样是使用静态生成,但是每隔10秒尝试重新生成这个页面 …

于是getStaticPaths也会重新调用(在无服务器函数上) ..

最后返回的数据仅仅在构建时预渲染路径,{fallback: blocking}将会服务端渲染页面(按需,如果页面不存在) …

处理流程:

  • 当请求发送到一个已经在构建时预渲染的页面时,它将初始化展示缓存的页面 …
  • 在初始化请求之后的对于这个页面的10秒中之内的任何请求都是被缓存的且即时的 ..
  • 10秒窗口期之后,下一个请求仍然会被缓存(陈旧的),但是会在背后重新触发页面的生成 …
  • 一旦页面生成成功,Next.js 将会无效缓存并且展示更新的页面,如果背后生成失败,旧的页面仍然能够继续展示 …
  • 当请求发送到一个还没有生成的路径上(这很显然是动态路由),next.js将会服务端渲染页面(在第一次) …

后续的请求直接返回缓存中提供的静态文件, ISR 在Vercel上具有更多详细的介绍 persists the cache globally and handles rollbacks

需要注意: 如果上游数据提供器已经默认缓存启动,你可能需要禁用(例如: useCdn: false),否则一个重新验证不能够拉去数据去更新ISR 缓存,缓存能够发生在CDN上(一个已经被请求的端点),当它返回了一个Cache-Controlheader的时候 ..

按照需要进行重新验证

如果将revalidate设置为60秒,那么所有的浏览者都会看到相同生成的内容(在一分钟内),仅仅有一种方式能够重新无效这个缓存(在一分钟之后某个人浏览了这个页面) …

V12.2.0开始,Next.js 支持了按需增量静态重新生成去手动的清理特定页面的缓存 ,这能够更容易的更新站点 ..

  • 当来自CMS的内容已经被创建或者更新时
  • 电子贸易元数据改变(价格,描述,分类,review,等其他)

getStaticProps,你不需要去指定revaldate去使用一个按需重新验证,如果revalidate已经省略,next.js 将会使用默认值false(不进行重新验证)并且仅仅在revalidate()调用时进行页面的按需验证 …

注意:

对于按需ISR 请求,中间件将不会执行,相反,在想要重新验证的路径上调用revalidate() ,例如,如果你有一个pages/blog/[slug].js并且存在一个重写/post-1-> /blog/post-1,你需要调用res.revalidate('/blog/post-1')

也就是在请求一个没有按需生成的路由并通过中间件跳转到按需生成路由,是对按需ISR 请求上(中间件不执行的一个逃生技巧) … - 但是这是我的理解 ..

而在后续使用中,使用通过API 路由对特定的按需ISR 请求(也就是路径)进行缓存清除 …

使用按需重新校验

首先,创建一个只有你的next.js 应用才知道的 密钥 token,这个密钥将被用来阻止未认证的访问重新校验API 路由 .. 你能够访问这个路由(手动或者webHook) - 通过以下的这种URL 结构 ..

  1. https://<your-site.com>/api/revalidate?secret=<token>

接下来,将secret作为一个环境变量放置到你的应用中,最终,创建重新校验API 路由:

  1. // pages/api/revalidate.js
  2. export default async function handler(req, res) {
  3. // Check for secret to confirm this is a valid request
  4. if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
  5. return res.status(401).json({ message: 'Invalid token' })
  6. }
  7. try {
  8. // this should be the actual path not a rewritten path
  9. // e.g. for "/blog/[slug]" this should be "/blog/post-1"
  10. await res.revalidate('/path-to-revalidate')
  11. return res.json({ revalidated: true })
  12. } catch (err) {
  13. // If there was an error, Next.js will continue
  14. // to show the last successfully generated page
  15. return res.status(500).send('Error revalidating')
  16. }
  17. }

View our demo

在开发阶段测试按需ISR ..

当在本地通过next dev开始运行时,getStaticProps在每次请求的时候都会执行 … 为了正确验证你的按需ISR 配置是正确的,你需要创建一个生产构建并启动生产服务器 ..

  1. next build
  2. next start

然后,你能够确保静态页面是成功的被校验 …

错误处理以及重新验证

getStaticProps中发生了错误(当在处理后台重新生成的时候),或者手动抛出了一个错误,那么将使用之前生成的页面进行展示,在下一次后续的请求中,Next.js将重新尝试调用getStaticProps

  1. export async function getStaticProps() {
  2. // If this request throws an uncaught error, Next.js will
  3. // not invalidate the currently shown page and
  4. // retry getStaticProps on the next request.
  5. const res = await fetch('https://.../posts')
  6. const posts = await res.json()
  7. if (!res.ok) {
  8. // If there is a server error, you might want to
  9. // throw an error instead of returning so that the cache is not updated
  10. // until the next successful request.
  11. throw new Error(`Failed to fetch posts, received status ${res.status}`)
  12. }
  13. // If the request was successful, return the posts
  14. // and revalidate every 10 seconds.
  15. return {
  16. props: {
  17. posts,
  18. },
  19. revalidate: 10,
  20. }
  21. }

自托管ISR

ISR 在自托管Next.js 站点已经可以开箱即用了 … 通过 next start ..

例如把它部署到K8S或者 其他云平台上 .. 默认生成的资源将会存储在每一个pod的内存中 … 这意味着每一个pod将有一份对于静态文件的copy .. 陈旧数据也许会展示到特定pod已经通过请求进行更新了资源 …

为了确保在所有pod中都是一致的,你能够禁用到基于内存的缓存,这将通知Next.js服务器仅仅利用由ISR在服务器系统生成的 资源 ..

你能够使用共享网络挂载到你的 k8s pods上(或者类似于配置)并在不同容器上重用相同文件系统缓存.. 通过共享挂载,.next目录将包含 next/image缓存也能够共享并重用 …

为了禁用内存缓存,设置 isrMemoryCacheSize= 0 到 next.config.js中 ..

  1. module.exports = {
  2. experimental: {
  3. // Defaults to 50MB
  4. isrMemoryCacheSize: 0,
  5. },
  6. }

你可能需要考虑多个pods中同时更新缓存的并发条件,依赖于你的共享挂载是如何配置的 …