可以注册邮箱的网站,个人怎么做ckmov解析网站,可以做公司网站,百度站长统计工具NextJs 渲染篇 - 什么是CSR、SSR、SSG、ISR 和服务端/客户端组件 前言一. 什么是CSR、SSR、SSG、ISR1.1 CSR 客户端渲染1.2 SSR 服务端渲染1.3 SSG 静态站点生成① 没有数据请求的页面② 页面内容需要请求数据③ 页面路径需要获取数据 1.4 ISR 增量静态再生1.5 四种渲染方式的对… NextJs 渲染篇 - 什么是CSR、SSR、SSG、ISR 和服务端/客户端组件 前言一. 什么是CSR、SSR、SSG、ISR1.1 CSR 客户端渲染1.2 SSR 服务端渲染1.3 SSG 静态站点生成① 没有数据请求的页面② 页面内容需要请求数据③ 页面路径需要获取数据 1.4 ISR 增量静态再生1.5 四种渲染方式的对比和总结 二. 服务端组件和客户端组件2.1 水合Hydration2.2 Suspense 和 Streaming2.3 React Server Components 和 SSR2.4 服务端组件 VS 客户端组件 前言
在 NextJs 初级篇 中讲了关于NextJs的安装、路由、中间件等内容本篇文章来一起学习一下关于 NextJs 的渲染知识。
一. 什么是CSR、SSR、SSG、ISR
我们先来说下这几个名词的专业解释
CSRClient-side Rendering客户端渲染。SSRServer-side Rendering服务端渲染。SSGStatic Site Generation静态站点生成。ISRIncremental Static Regeneration增量静态再生。
接下来我们对每种渲染进行详细的解释以及NextJs的实现案例。后续都用简称来说明。
1.1 CSR 客户端渲染
CSR 常规的实现就是我们常规的React开发就是一种客户端渲染
一般浏览器会下载一个非常小的HTML文件以及必要的JS文件。我们在JS中发送请求更新DOM和渲染页面。比如useEffect钩子函数中初始化页面数据。
NextJs 中在AppRouter模式下使用CSR在组件中使用 use client 标明用useEffect请求初始化数据渲染即可例如以下伪代码
use client
import React, { useEffect, useState } from react
const Home () {const [data, setData] useStateany(null);useEffect(() {setTimeout(() {setData({ id: 1 })}, 5000);})return {data ? span idtest{data.id}/span : Loading}/
}export default Home刚开始的时候页面长这个样子 渲染完毕后
1.2 SSR 服务端渲染
SSR 服务渲染有啥好处我们举个例子假如客户端网速非常差那么在CSR的情况下由客户端发起请求加载数据就会非常慢倘若我们把加载数据的工作丢给服务端而服务器的网络情况非常良好那么最终的首屏加载时长FCP也就更短。
但是同样的由于SSR情况下它的响应时长还算上了数据的请求因此响应时间更长最终的TTFB指标也就更长。
例如NextJs中要想实现SSR我们可以在pages目录下创建个ssr.tsx文件
内容如下我们需要借助getServerSideProps函数来获取数据并通过props返回给前端组件
// pages/ssr.js
export async function getServerSideProps() {const data [{ id: 1, name: ljj }]return { props: {data} }
}
// getServerSideProps 传入的是什么这里就接收什么名称的参数
const SSR ({ data }: any) {return span idtest{JSON.stringify(data)}/span
}
export default SSR;1.3 SSG 静态站点生成
SSR 会在构建阶段就将页面编译成一个静态的HTML文件。
例如当我们的站点上面的Layout总是一样的时候或者是面对所有的用户展示的都是一个内容那么这块部分就没必要在用户请求页面的时候来渲染。干脆提前编译为HTML文件在用户访问的时候直接返回一个HTML则会更快。
NextJs 中实现SSG分为这么几种情况
① 没有数据请求的页面
例如
const SonA () {return 我是SonA/
}
export default SonA这种页面NextJs 在构建的时候就会生成一个单独的HTML文件
② 页面内容需要请求数据
如果我们的HTML文件的某些内容需要通过接口获取那怎么办这种方式就需要结合 getStaticProps 函数来使用。例如我们在pages目录下创建ssg.tsx文件
export default function SSG({ data }: any) {return (ul{data.map((item: any) (li key{item.id}{item.title}/li))}/ul)
}
export async function getStaticProps() {const res await fetch(https://jsonplaceholder.typicode.com/posts)const data await res.json()return {props: {data,},}
}getStaticProps 这个函数会在构建的时候被调用然后通过props属性传递给组件。
③ 页面路径需要获取数据
我们知道NextJs 中有一个动态路由只需要将动态部分用[]括起来即可例如 那如果我们希望这类路由的页面都通过SSG来实现
blog/1blog/2…
如何实现我们在 getStaticProps 的基础上追加一个函数的实现 getStaticPaths
export default function Blog({ post }: any) {return (header{post.title}/headermain{post.body}/main/)
}
export async function getStaticPaths() {const res await fetch(https://jsonplaceholder.typicode.com/posts)const posts await res.json()const paths posts.map((post: any) ({params: { id: String(post.id) },}))return { paths, fallback: false }
}export async function getStaticProps({ params }: any) {// 如果路由地址为 /posts/1, params.id 为 1const res await fetch(https://jsonplaceholder.typicode.com/posts/${params.id})const post await res.json()return { props: { post } }
}getStaticProps 用来定义获取的数据传递给HTMLgetStaticPaths 则用来定义哪些路径将会实现SSG。fallback 返回false代表当访问这些静态路径以外的则返回404. 当我们执行npm run build的时候可以看到构建产物如下这些都是SSG的产物。 1.4 ISR 增量静态再生
我们的一些页面例如博客主题内容可能永远是不变的但是部分内容是改变的例如这篇博客的阅读量。在我们使用SSG的情况下这个HTML文件就被固定生成了那么如何让这个阅读量能够实时的改变呢那么在SSG的基础上就有了ISR。
在访问某个SSG页面的时候可能依旧是老的HTML内容。但是与此同时NextJs 会静态编译一个新的HTML文件。那么在第二次访问的时候就会变成新的HTML文件内容了。
我们在案例的基础上稍微改造一下
export default function Blog({ post }: any) {return (header{post.title}/headermain{post.body}/main/)
}
export async function getStaticPaths() {const res await fetch(https://jsonplaceholder.typicode.com/posts)const posts await res.json()const paths posts.map((post: any) ({params: { id: String(post.id) },}))return { paths, fallback: blocking }
}
function getRandomInt(max: number) {return Math.floor(Math.random() * max);
}
export async function getStaticProps({ params }: any) {const res await fetch(https://jsonplaceholder.typicode.com/posts/${getRandomInt(100)})const post await res.json()return {props: { post },revalidate: 3,}
}可以看到我们在 getStaticProps 函数中多暴露了一个属性revalidate。代表发生请求的时候需要间隔多少秒才会更新页面我这里填的是3也就是3秒会刷新一次构建新的HTML。
注意ISR需要在生产环境下生效。因此我们npm run build 之后再npm run start 结果如下 可以看到我们第一次访问以及接下来的3秒内博客的内容都是一样的。但是3秒过后博客的内容就发生了改变实际上是HTML刷新了。每3秒就会重新构建一个新的HTML缓存3秒的时长。
1.5 四种渲染方式的对比和总结
CSRSSRSSGISR名词解释客户端渲染服务端渲染静态站点生成即生成HTML文件返回给客户端增量静态再生实现方式例如React的useEffect借助getServerSideProps函数在服务端请求数据并通过props属性传递给组件①没有数据请求的页面自动生成HTML ②文件内容则借助 getStaticProps 函数获取数据再生成静态文件 ③ 动态路由则借助getStaticPaths来指定生成HTML的路径在SSG的基础上getStaticProps函数追加暴露revalidate属性代表刷新HTML的时长优缺点只有少量的静态文件先加载由客户端发起请求触发渲染 TTFB 短。但是在网络特别差的情况下会大大增加FCP首屏加载时长可以让初始化请求交给服务端完成由服务端完成渲染解决客户端网络不一的情况FCP缩短但是会增加响应时长TTFB时长高。每次请求都会触发SSG渲染。可以让页面生成静态HTML在编译时机就可以完成构建只会触发一次。可以控制HTML的刷新时长在指定的时间范围内使用同一个HTML时间过后自动重新构建
二. 服务端组件和客户端组件
在第一节当中我们讲到了SSR在 NextJs v12之前都是通过 getServerSideProps 这个函数来实现服务端渲染即SSR。
2.1 水合Hydration
SSR 服务端渲染会将整个组件渲染为HTML但是HTML是没有交互性的。而客户端在渲染HTML之后还需要等待JS下载完毕并且执行由JS来赋予HTML交互性那么这个阶段就叫做水合。水合过后内容就会变为可交互性。
那么SSR有这么几个缺陷
SSR渲染数据的获取必须在组件渲染之前。组件的JS必须先加载到客户端才能开始水合。所有组件都必须水合完毕组件之间才能够进行交互。
因此一旦有部分组件渲染慢了就会导致整体的渲染效率降低。不仅如此SSR 只能适用于页面的初始化加载对于后续的页面交互、数据修改等操作SSR 就无作用了。
2.2 Suspense 和 Streaming
上面提到了服务端只能在获取所有数据后渲染 HTMLReact 只能在下载了所有组件代码后才能进行水合。
为了解决这个问题就有了 Suspense 组件它允许你推迟渲染某些内容直到满足某些条件例如数据加载完毕
给个案例如下
import { Suspense } from reactconst sleep (ms: number) new Promise(r setTimeout(r, ms));async function Component1() {await sleep(2000)return h1Hello Component1/h1
}async function Component2() {await sleep(3000)return h1Hello Component2/h1
}async function Component3() {await sleep(4000)return h1Hello Component3/h1
}export default function MySuspense() {return (section style{{ padding: 20px }}Suspense fallback{pLoading Component1/p}Component1 //SuspenseSuspense fallback{pLoading Component2/p}Component2 //SuspenseSuspense fallback{pLoading Component3/p}Component3 //Suspense/section)
}效果如下 这种方式我们可以看下请求头 Transfer-Encoding 的值为 chunked表示允许 HTTP由网页服务器发送给客户端应用 通常是网页浏览器的数据可以分成多个部分
倘若我们这三个组件都不使用Suspense封装效果如下 整体的效果一目了然。不使用Suspense封装的情况下需要等待所有组件都渲染完毕才能完整的展示页面。
而 Suspense 背后的实现技术就叫做Streaming。即将页面的HTML 拆分多个chunks逐步从服务端发送给客户端。有这么几个好处
提前发送到客户端的组件就可以提前进行水合那么用户就可以和提前水合完毕的组件进行交互。从页面性能角度来考虑就是减少 TTFB 和 FCP 以及 TTI的时长。有兴趣的可以看下我这篇文章 性能优化 - 前端性能监控和性能指标计算方式
传统的SSR 使用Streaming之后
那么在NextJs中有两种实现Streaming的方式
针对组件级别使用Suspense组件就上面的案例。针对页面级别使用loading.tsx。
例如这样的目录结构 组件1
const sleep (ms: number) new Promise(r setTimeout(r, ms));export default async function Component1() {await sleep(2000)return h1Hello Component1/h1
}组件2
const sleep (ms: number) new Promise(r setTimeout(r, ms));export default async function Component2() {await sleep(3000)return h1Hello Component2/h1
}page.tsx
import Link from next/link
export default function MySuspense({ children }) {return (sectionnav classNameflex items-center justify-center gap-10 text-blue-600 mb-6Link href/suspense/component1component1/LinkLink href/suspense/component2component2/Link/nav{children}/section)
}loading.tsx
export default async function loading() {return h1loading..../h1
}效果如下
2.3 React Server Components 和 SSR
RSCReact Server Components和 SSR 的区别
RSC重点在Components即组件。提供了更细粒度的组件渲染方式可**以在组件中直接获取数据。组件依赖的代码并不会打包到bundle中。并且只有在客户端请求相关组件的时候才会返回。 **SSR重点在Rendering即渲染。在服务端将组件渲染成HTML发送给客户端因此SSR需要将组件的所有依赖都打包到bundle中。
Suspense以及Streaming的实现确实能优化我们的页面渲染将原本只能先获取数据、再渲染水合的传统 SSR 改为渐进式渲染水合 。
但是对于用户需要下载的JS代码量依旧是没有减少。因此使用RSC服务端组件就能将不必要的代码隐藏到服务器当中。
2.4 服务端组件 VS 客户端组件
在 NextJs 中组件默认就是服务端组件。这类组件请求会在服务端执行最后会将组件渲染成HTML返回给客户端例如以下就是一个服务端组件的例子
const Address async () {const res await fetch(https://jsonplaceholder.typicode.com/posts)const data (await res.json()).slice(0, 10)console.log(data)return ul{data.map(({ title, id }: any) {return li key{id}{title}/li})}/ul
}
export default Address相关的console打印会在服务端执行 数据的渲染也会直接在HTML当中。 那么再来看下对应的客户端组件版本
使用 use client 声明。配合 useEffect 钩子函数
use client
import { useEffect, useState } from react;const Address () {const [list, setList] useState([]);const fetchData async () {const res await fetch(https://jsonplaceholder.typicode.com/todos)const data (await res.json())setList(data)}useEffect(() {fetchData()}, [])return ul{list.map(({ title, id }: any) {return li key{id}{title}/li})}/ul
}export default Address两者对比的优势如下
服务端组件客户端组件优势① 数据获取更快。 ② 安全服务端逻辑不会暴露给前端 ③ 缓存服务端渲染的结果可缓存 ④ 服务端组件的代码不会打包到bundle中 ⑤ FCP时长更短 ⑥ 可以使用Streaming将渲染工作拆分为chunks通过流式传输到客户端用户可以更早的看到部分页面而无需等待整个页面渲染完毕。① 交互性更好可以使用useEffect、useState等钩子函数。 ② 可以使用浏览器的API劣势不可使用useEffect、useState等钩子函数也就无法管理状态网络很差的情况下由客户端完成渲染会导致FCP特别长运行时机服务端组件运行在构建时和服务端运行在构建时、服务端生成初始HTML和客户端管理DOM
除此之外还有几个非常重要的点
服务端组件可以直接导入客户端组件但客户端组件并不能导入服务端组件。服务端组件当导入到客户端组件中就会被认为是客户端组件。