Next.js性能优化之ISR渲染入门和原理探索

article/2025/9/13 19:47:49

前言

术语说明:

  • SSR —— 服务端渲染
  • SSG —— 静态生成
  • ISR —— 增量静态化
  • Date Fetch 函数 —— 本文特指服务端数据获取的几种函数 getStaticPropsgetServerSidePropsgetInitialPropsgetStaticPaths

Next.js 中最突出的莫过于它的渲染模式,之前写过一篇文章 《Next.js之前端渲染模式》 中分别介绍和对比了几种渲染模式的优劣势,而且其中的 ISR 在大部分的业务页面中能起到很关键性的性能优化作用。

最近在使用 ISR 功能的时候遇到了一些问题,本篇文章将分享如何更好的去使用 ISR,以及在使用的过程中可能会遇到的一些问题和解决方法,其中会涉及到一些原理的探索。

为何要使用 ISR

先分享一下为何要使用 ISR ,可能有人还不是很理解这个 ISR 是什么。

Next.JS 项目打包时,使用 getStaticProps 或者不使用 Date Fetch 函数的页面会默认 静态化 ,也就是会生成[pageName].html 的 html 文件,用户访问就后 Next 服务直接读取此 html 文件,不再去动态渲染内容,也就减少了接口的请求,因此,相比 SSR 渲染模式,能极大的减少页面访问时间(大部分页面在正常网速能控制在 1s 内显示)和降低服务器的压力。

使用 getStaticProps 的静态化页面会导致了一个问题,接口内容更新后,用户访问页面获取到的信息并不会更新,因此需要一种可以在服务运行中动态去触发 SSG 生成的 html 的能力,于是就出现了 ISR,让 SSG 也能拥有增量更新的能力。

使用和功能验证

增量静态化一般有两种使用方式:定时更新指令更新

写了一个 demo 工程,可以去自己 clone 下来尝试,下面会也会详细介绍demo实现的主要过程和用里面的 3 个 demo 示例的运行结果来检验一些理论。

ISR —— 定时更新

新建页面:

// src/pages/isr/demo1/index.js
const Demo1 = ({ times }) => {return (<div><h2>定时刷新</h2><div>刷新次数:{times}</div></div>)
}let times = 0;export async function getStaticProps() {times += 1console.log(times)return {props: {times,},revalidate: 10, // 10秒后访问触发更新}
}
复制代码

开发环境 静态化 表现和 ssr 一样,因此,需要需要打包后运行才能看出效果,运行 build 命令:

# 构建打包
pnpm build
复制代码

构建过程中会打印 1 这时候可以看一下构建产物:

构建产物 .next/server/pages/isr 目录下已经生成了一个 demo1 的 html 文件,注意这时候 times1

pnpm start 启动工程后访问demo1页面:

vscode 的控制台打印输出为1(build 和 start 都会重新初始化 times),这时候会触发构建;

构建完成之后,再次访问会发现跟首次访问一样的,上面显示的刷新次数还是 1 ,但是 vscode 的控制台印输出为2 ,这是因为访问的时候是去构建新的页面,仍然返回上一次构建成功的页面内容,并不会返回当前正在构建的内容。以后每次访问时,控制台打印的数字都会比正在显示的大。

上面的这种现象也能说明官方文档上阐述的一个结论:

When a request is made to a page that was pre-rendered at build time, it will initially show the cached page 当发出请求让页面进行构建时,它会先返回缓存页面。

ISR —— 指令更新

我们新建一个 demo2 页面,复制 demo1 即可,然后去除 getStaticProps 函数返回的 revalidate 字段:

// src/pages/isr/demo2/index.js
export async function getStaticProps() {times += 1console.log(times)return {props: {times,}}
}
复制代码

新增一个触发页面更新的接口指令:

// src/pages/api/revalidate.js
export default async function revalidateHandler(req, res) {// 指令密钥校验if (req.query.secret !== MY_SECRET_TOKEN) {return res.status(401).json({ message: 'Invalid token' })}try {// 更新 demo2await res.revalidate('/isr/demo2')// 返回说明更新指令已发出,并不能说明一定更新成功return res.json({ revalidated: true })} catch (err) {// 更新失败return res.status(500).send('Error revalidating')}
}
复制代码

运行 pnpm build && pnpm start ,访问 demo2 页面 不管访问多少次,getStaticProps 都不会触发,页面上都显示刷新次数为 1

然后访问 http://localhost:3000/api/revalidate?secret=MY_SECRET_TOKEN,访问后,电脑控制台会打印2,但如果立马快速去访问页面,页面上显示刷新次数还会是 1 ,因为构建还是需要一定的时间,稍微等一下再去访问就成了 2,一般时间在一两秒以上。

demo 工程我部署到了 vercel,可以点击 页面 和 指令 直接去验证,不过因为是外网,有可能访问不了。

ISR —— 动态路由

上面的两个案例只是一个单页面,如果是动态路由页面,那么也可以添加 getStaticPaths 来实现。

demo3 在 demo2 的基础上,修改了一下文件名命名方式,demo3/index.js 改成 demo3/[id].js,内容新增 getStaticPaths 内容:

export async function getStaticPaths() {// 从接口获取文章列表,使用的时候不用本站的接口,这样在打包的时候会出错。const res = await fetch('http://localhost:3000/api/posts/list')const posts = await res.json()// 处理成 getStaticPaths 需要的返回参数const paths = posts.map((post: any) => ({params: { id: post.id },}))return { paths, fallback: 'blocking' }
}
复制代码

对应的修改一下页面内容:

const Demo3 = ({ name, content }) => {return (<div><h2>{name}</h2><div dangerouslySetInnerHTML={{ __html: content }}></div></div>)
}export async function getStaticProps(ctx) {// 获取详情const res = await fetch(`http://localhost:3000/api/posts/detail?id=${ctx.params?.id}`);const detail = await res.json()return {props: {...detail}}
}
复制代码

src/pages/api/posts 目录下编写了两个简单的测试接口,这个可以直接去代码里面看,也可以忽略,并不影响。

下面新增动态指令:

// src/pages/api/revalidate/[id].js
export default async function revalidateHandler(req, res) {const { id } = req.query// ...省略try {// 更新 demo3await res.revalidate(`/isr/demo3/${id}`)// 返回说明更新指令已发出,并不能说明一定更新成功return res.json({ revalidated: true })} catch (err) {// 更新失败return res.status(500).send('Error revalidating')}
}
复制代码

使用 getStaticPaths 来获取需要动态生成的页面时,大部分场景可能不需要使用接口请求获取列表,且不能请求本工程定义的 api 接口,因为只会在构建过程中执行,使用本工程的 api 会报错,而且这里请求接口,如果需要生成的页面过多,会导致构建很慢,一般直接返回 { paths: [], fallback: 'blocking' } 即可,这样在页面 首次访问(首个访问页面的用户进行首次访问时会比较慢) 或者被下发 revalidate指令 时就可以生成页面内容。可以打开 demo3,或者本地打开 http://localhost:3000/isr/demo3/[id] 试一下,把 [id] 替换成什么数字都可以。

注意事项:

  • 动态路由主要需要注意 fallback ,默认为 false ,这时任何未使用 paths 字段返回的路径都将会显示 404 页面,blocking 则允许传入其他动态路由参数,一般使用 blocking
  • 构建指令并不一定需要动态指令,也可以全部页面共用一个 revalidateHandler ,使用 query 参数来控制,根据具体情况使用即可,但必须注意安全性问题。

部署遇到的问题和解决方法

现在主要遇到多进程的疑惑和多服务器负载均衡的问题,后续有更多问题会更新到本篇文章,欢迎大家关注➕点赞👍➕收藏

多进程负载均衡的疑惑

部署到线上是使用 pm2 cluster 模式启动,因此会启动多个进程,然后就考虑到了一个问题:

next 服务启动后,每次访问页面,到了 server 端都是只会被分发到一个进程去处理,首次访问就会动态生成一页面的 html 文件,那么第二次访问进入到了第二个进程,这里是不是会读取磁盘内的 html 内容呢?

答案是否定的,经过我多次测试验证,再加上阅读了对应的相关源码,然后得出了一个结论:

ISR 生成 html 文件后,默认并不会读取它,因为写入磁盘之前,存入到了内存中,也就是加了一个变量进行缓存 html 字符串。

如果这时候去手动改动 html 文件,会验证上面这个结论。

next 多进程这里的处理方式也很有意思,每次访问新的进程,都会重新去生成一次 html 文件,但是当次的访问请求会返回旧的缓存页面,如果没有旧的内容才返回新的内容。每个进程生成页面后都会被记录,下次访问不再重现生成。

多服务器负载均衡怎么处理

前面得出 next 服务启动后,不会再读取磁盘的 html 内容,也就是如果是多台服务器,就会出现这样一个bug:

每台服务器都是独立的,磁盘上生成的 html 会可能不一样,server 内存中缓存的 html 也可能不一样。那么可能多次访问,用户显示的结果却可能不一样。

那怎么处理呢?想到了两种解决方法:

  • 对页面的 更新指令 进行多服务器下发,因为更新指令一般频率比较少,因此不会造成性能上的问题,但是这种只能处理 指令更新 的情况
  • 共享磁盘➕禁用内存缓存:next 提供了配置参数来禁用 ISR 缓存,这种方法可以处理各种情况,但是性能稍微有一点点影响,但影响并不大,每个 html 内容也不会太大

更推荐第二种方式,禁用缓存的方法:

module.exports = {experimental: {// 默认最大缓存限制为 50MBisrMemoryCacheSize: 0,},
}
复制代码

也可以看一下官方文档说明

总结

本文从 ISR 介绍、使用、遇到的问题 三个方面来分析 增量静态化 ,其中也涉及到了一些原理分析以及需要注意的事项,如果有不清楚欢迎评论区讨论。

下面再总结一下使用 增量静态化 的一些问题和注意事项:

  1. 如果直接修改了静态化 html 的内容,那么访问的时候页面会先显示修改后的内容,但 next 会进行检测页面内容是否正确,不正确会刷新页面内容,但不会纠正磁盘内的 html 文件。
  2. 如果修改了 ISR 的静态化 html 的内容,那么首次访问时,也会出现上面那个刷新页面现象,这时会生成新的 html 还会纠正磁盘内的 html 文件,但是再次去修改 html 时,不会出现刷新页面现象,但如果访问的是还未访问的新进程,会纠正磁盘内的 html 文件,如果是不是首次访问时,就永远不会纠正
  3. 第 2 种情况,如果禁用 ISR 缓存的话,每个进程的首次会纠正磁盘内的 html 文件,如果是不是首次访问时,就永远不会纠正。但还是会刷新页面内容。
  4. 不管是定时还是指令方式,当发出请求让页面进行构建时,它都会先返回缓存页面。
  5. fallback 更建议设置为 blocking ,防止构建时间过长。
  6. pm2 多进程启动项目不用担心每个进程返回结果不一样,next 内部已经进行处理
  7. 多服务器负载时,建议使用 共享磁盘➕禁用内存缓存 会更好。


http://chatgpt.dhexx.cn/article/idhb9CLm.shtml

相关文章

Kafka之分区副本与ISR

概念 什么是副本 Kafka的Topic分区本质是一个用于存储Topic下的消息的日志&#xff0c;但是只存一份日志会因为机器损坏或其他原因导致消息丢失不可恢复&#xff0c; 因此需要多个相同的日志作为备份&#xff0c;提高系统可用性&#xff0c;这些备份在kafka中被称为副本(rep…

Kafka的ISR收缩机制

ISR什么时候收缩ISR什么时候扩展ISR的传播机制Broker宕机之后怎么ISR的收缩&#xff1f; Kafka在启动的时候,会启动一个副本管理器ReplicaManager,这个副本管理器会启动几个定时任务。 ISR过期定时任务isr-expiration,每隔replica.lag.time.max.ms/2毫秒就执行一次。ISR变更的…

Kafka ISR

ISR&#xff08;in-sync replica&#xff09; 就是 Kafka 为某个分区维护的一组同步集合&#xff0c;即每个分区都有自己的一个 ISR 集合&#xff0c;处于 ISR 集合中的副本&#xff0c;意味着 follower 副本与 leader 副本保持同步状态&#xff0c;只有处于 ISR 集合中的副本才…

kafka中的ISR、AR又代表什么?ISR伸缩又是什么?

kafka中的ISR、AR又代表什么&#xff1f;ISR伸缩又是什么&#xff1f; ​ 分区中的所有副本统称为AR&#xff08;Assigned Repllicas&#xff09;。所有与leader副本保持一定程度同步的副本&#xff08;包括Leader&#xff09;组成ISR&#xff08;In-Sync Replicas&#xff09…

Kafka之ISR机制的理解

Kafka对于producer发来的消息怎么保证可靠性&#xff1f; 每个partition都给配上副本&#xff0c;做数据同步&#xff0c;保证数据不丢失。 副本数据同步策略 和zookeeper不同的是&#xff0c;Kafka选择的是全部完成同步&#xff0c;才发送ack。但是又有所区别。 所以&…

中断ISR技术架构

架构一 ISR采用立即响应思路&#xff0c;技术架构如下图&#xff1a; 优点&#xff1a;简单。 缺点&#xff1a;处理性能不高&#xff0c;中断优先级规划性不高(仅仅区分CPU的32个优先级别&#xff0c;针对不同类型中断优先级不支持)。 选型&#xff1a;对于硬件支持多级中断…

【Java面试】什么是 ISR,为什么需要引入 ISR

Hi&#xff0c;大家好&#xff0c;我是Mic。 一个工作5年的粉丝&#xff0c;在简历上写精通Kafka。 结果在面试的时候直接打脸。 面试官问他&#xff1a;“什么是ISR&#xff0c;为什么需要设计ISR” 然后他一脸懵逼的看着面试官。 下面看看普通人和高手的回答。 需要高手面试文…

2022年正式赛题网络系统管理Linix模块 NFS部分

共享/webdata/目录;用于存储AppSrv主机的WEB数据;仅允许AppSrv主机访问该共享;考虑安全,不论登入NFS 的使用者身份为何,都将其设置为匿名用户访问。

NXP迅为i.MX8Mmini开发板Linix固件编译下

4 编译 Ubuntu20 桌面版本 1 如果大家想要编译 Ubuntu20 桌面版本&#xff0c;首先要将光盘资料“iTOP-i.MX8MM 开发板\01-i.MX8MM 开发板光盘资料\20210830\07-Ubuntu20 系统源码\Ubuntu20 桌面版本” 下的压缩包拷贝到 Linux 源码的根目录下&#xff0c;解压压缩包得到 ubunt…

Linix(CentOS6.5)详细安装

CentOS6.5的安装&#xff08;Minimal&#xff09; 点击CentOS6镜像文件下载 点击CentOS7镜像文件下载 1.点击创建新的虚拟机 2.选择自定义模式 3.选择VMware Workstation的版本 4.选择稍后安装 5.选择合适操作系统&#xff08;64位OR32位&#xff09; 6.更改默认的安装…

Linix环境搭建及概述

linux环境搭建及概述 前言 Linux 的安装&#xff0c;安装步骤比较繁琐&#xff0c;现在其实云服务器挺普遍的&#xff0c;价格也便宜&#xff0c;如果直接不想搭建&#xff0c;也可以直接买一台学习用用&#xff01;废话不多说直接开整 一、安装CentOS&#xff08;虚拟机安装…

Linux系统常用命令--LInix系统随笔(四)

前言&#xff1a;虽然一直在用linux但是一直没有系统的学习过&#xff0c;趁着暑假花了几天看着鸟哥的书学习了一下。下面是我记录的一些笔记&#xff0c;本人属于入门的小白所以难免有不足之处&#xff0c;还望发现的表哥们多多指正。 ①命令格式与目录处理命令ls ls--list…

util-linix 实用程序包中包含了许多系统管理员常用的其它命令

util-linix 实用程序包中包含了许多系统管理员常用的其它命令。这些实用程序是由 Linux 内核组织发布的&#xff0c;这 107 条命令中几乎每一个都来自原本是三个单独的集合 —— fileutils、shellutils 和 textutils&#xff0c;2003 年它们被合并成一个包&#xff1a;util-lin…

LINIX 通过进程号查端口、通过端口查进程号

可以通过 netstat -nlp|grep pid或port&#xff0c;来查询端口、进程号 1.通过进程查PORT 2.通过PORT查进程 netstat命令参数说明 n 直接使用ip地址&#xff0c;而不通过域名服务器 -l 显示监控中的服务器的 Socket -p 显示正在使用 Socket 的程序识别码和程序名称

Linu X

LINU X 基本命令 / 根目录 几个盘几个根目录 dev 设备目录 boot 启动文件 etc 配置目录 home 家目录 用户1.管理员 2.一般用户 proc硬件信息 【benlocalhost desktop]#管理员 管理员 主机名 当前目录 $普通用户 cd 修改&#xff0c;进入当前目录 ls显示当前目录下的项目 ll显示…

linx

linx ls -l 命令详解 上图用ls -l命令查看某一个目录会得到一个7个字段的列表 1. 文件类型     “-”表示普通文件&#xff1b; “d”表示目录&#xff1b; “l”表示链接文件&#xff1b; “p”表示管理文件&#xff1b; “b”表示块设备文件&#xff1b; “c”表示…

Linux-

文章目录 基础知识Linux使用命令整理Linux系统下文件类型颜色表示含义 基础知识 Linux使用命令整理 zip 文件名——&#xff08;压缩命令&#xff09;将所有.jpg的文件压缩成一个zip包 &#xff0c;案例&#xff1a;zip all.zip &#xff1b;zip *.jpg unzip 文件名——&…

Linix

Linix 一、Linix的基本使用1.1 、Linux目录结构1.2、文件目录属性1.3、目文件展示1.4、用户及权限管理1.4.1、概述1.4.2、用户管理1.4.3、组管理1.4.4、权限管理 1.5、查看用户信息 查看用户组信息 二、命令相关2.1、系统相关1.4.4、权限管理 2.2、进程相关2.2.1、端口占用情况…

Linux目录结构与路径

目录 一、Linux目录结构 二、绝对路径与相对路径 一、Linux目录结构 Linux 系统中没有盘符的概念&#xff0c;所有的文件和目录都被组织成以一个根节点开始的倒置的树状结构&#xff0c; 文件系统的最顶层是由根目录开始的&#xff0c;系统使用 / 来表示根目录&#xff0c;呈…

Linux基础

Linix概述 unix 是多用户、多任务的操作系统&#xff0c;Linux是基于Unix的&#xff0c;Linux的版本分为两种&#xff1a;内核版本和发行版本&#xff1b;内核版本是指在 Linus领导下的内核小组开发维护的系统内核的版本号 Linux的远程访问&#xff1a;远程访问的软件:CRT lin…