NextJs 渲染篇 - 什么是CSR、SSR、SSG、ISR 和服务端/客户端组件

NextJs 渲染篇 - 什么是CSR、SSR、SSG、ISR 和服务端/客户端组件

  • 前言
  • 一. 什么是CSR、SSR、SSG、ISR
    • 1.1 CSR 客户端渲染
    • 1.2 SSR 服务端渲染
    • 1.3 SSG 静态站点生成
      • ① 没有数据请求的页面
      • ② 页面内容需要请求数据
      • ③ 页面路径需要获取数据
    • 1.4 ISR 增量静态再生
    • 1.5 四种渲染方式的对比和总结
  • 二. 服务端组件和客户端组件
    • 2.1 水合(Hydration)
    • 2.2 Suspense 和 Streaming
    • 2.3 React Server Components 和 SSR
    • 2.4 服务端组件 VS 客户端组件

前言

在 NextJs 初级篇 中讲了关于NextJs的安装、路由、中间件等内容,本篇文章来一起学习一下关于 NextJs 的渲染知识。

一. 什么是CSR、SSR、SSG、ISR

我们先来说下这几个名词的专业解释:

  • CSR(Client-side Rendering)客户端渲染
  • SSR(Server-side Rendering)服务端渲染
  • SSG(Static Site Generation)静态站点生成
  • ISR(Incremental Static Regeneration)增量静态再生

接下来我们对每种渲染进行详细的解释以及NextJs的实现案例。后续都用简称来说明。

1.1 CSR 客户端渲染

CSR 常规的实现就是我们常规的React开发,就是一种客户端渲染:

  1. 一般浏览器会下载一个非常小的HTML文件以及必要的JS文件。
  2. 我们在JS中发送请求,更新DOM和渲染页面。比如useEffect钩子函数中初始化页面数据。

NextJs 中,在AppRouter模式下,使用CSR,在组件中使用 'use client' 标明,用useEffect请求初始化数据渲染即可,例如以下伪代码:

'use client'
import React, { useEffect, useState } from 'react'
const Home = () => {
    const [data, setData] = useState<any>(null);
    useEffect(() => {
        setTimeout(() => {
            setData({ id: 1 })
        }, 5000);
    })
    return <>
        {data ? <span id='test'>{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 id='test'>{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/1
  • blog/2

如何实现?我们在 getStaticProps 的基础上,追加一个函数的实现 getStaticPaths

export default function Blog({ post }: any) {
    return (
        <>
            <header>{post.title}</header>
            <main>{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 为 1
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`)
    const post = await res.json()
    return { props: { post } }
}
  • getStaticProps 用来定义获取的数据传递给HTML
  • getStaticPaths 则用来定义哪些路径将会实现SSG
  • fallback 返回false代表当访问这些静态路径以外的,则返回404.
    当我们执行npm run build的时候,可以看到构建产物如下,这些都是SSG的产物。

在这里插入图片描述

1.4 ISR 增量静态再生

我们的一些页面例如博客,主题内容可能永远是不变的,但是部分内容是改变的,例如这篇博客的阅读量。在我们使用SSG的情况下,这个HTML文件就被固定生成了,那么如何让这个阅读量能够实时的改变呢?那么在SSG的基础上,就有了ISR

  1. 在访问某个SSG页面的时候,可能依旧是老的HTML内容。
  2. 但是与此同时,NextJs 会静态编译一个新的HTML文件。
  3. 那么在第二次访问的时候,就会变成新的HTML文件内容了。

我们在``案例的基础上,稍微改造一下:

export default function Blog({ post }: any) {
    return (
        <>
            <header>{post.title}</header>
            <main>{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文件返回给客户端)增量静态再生
实现方式例如ReactuseEffect借助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 'react'

const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));

async function Component1() {
    await sleep(2000)
    return <h1>Hello Component1</h1>
}

async function Component2() {
    await sleep(3000)
    return <h1>Hello Component2</h1>
}

async function Component3() {
    await sleep(4000)
    return <h1>Hello Component3</h1>
}

export default function MySuspense() {
    return (
        <section style={{ padding: '20px' }}>
            <Suspense fallback={<p>Loading Component1</p>}>
                <Component1 />
            </Suspense>
            <Suspense fallback={<p>Loading Component2</p>}>
                <Component2 />
            </Suspense>
            <Suspense fallback={<p>Loading Component3</p>}>
                <Component3 />
            </Suspense>
        </section>
    )
}

效果如下:
在这里插入图片描述
这种方式我们可以看下请求头:
在这里插入图片描述
Transfer-Encoding 的值为 chunked,表示允许 HTTP由网页服务器发送给客户端应用( 通常是网页浏览器)的数据可以分成多个部分

倘若我们这三个组件都不使用Suspense封装,效果如下:
在这里插入图片描述
整体的效果一目了然。不使用Suspense封装的情况下,需要等待所有组件都渲染完毕才能完整的展示页面。

Suspense 背后的实现技术就叫做Streaming。即将页面的HTML 拆分多个chunks,逐步从服务端发送给客户端。有这么几个好处:

  • 提前发送到客户端的组件,就可以提前进行水合,那么用户就可以和提前水合完毕的组件进行交互。
  • 从页面性能角度来考虑就是:减少 TTFBFCP 以及 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 <h1>Hello Component1</h1>
}

组件2:

const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));

export default async function Component2() {
    await sleep(3000)
    return <h1>Hello Component2</h1>
}

page.tsx

import Link from 'next/link'
export default function MySuspense({ children }) {
    return (
        <section>
            <nav className="flex items-center justify-center gap-10 text-blue-600 mb-6">
                <Link href="/suspense/component1">component1</Link>
                <Link href="/suspense/component2">component2</Link>
            </nav>
            {children}
        </section>
    )
}

loading.tsx

export default async function loading() {
    return <h1>loading....</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

两者对比的优势如下:

服务端组件客户端组件
优势数据获取更快。 ② 安全(服务端逻辑不会暴露给前端)缓存(服务端渲染的结果可缓存) ④ 服务端组件的代码不会打包到bundleFCP时长更短 ⑥ 可以使用Streaming,将渲染工作拆分为chunks通过流式传输到客户端,用户可以更早的看到部分页面,而无需等待整个页面渲染完毕① 交互性更好,可以使用useEffect、useState等钩子函数。 ② 可以使用浏览器的API
劣势不可使用useEffect、useState等钩子函数,也就无法管理状态网络很差的情况下,由客户端完成渲染会导致FCP特别长
运行时机服务端组件运行在构建时和服务端运行在构建时、服务端(生成初始HTML)和客户端(管理DOM

除此之外,还有几个非常重要的点:

  1. 服务端组件可以直接导入客户端组件,但客户端组件并不能导入服务端组件。
  2. 服务端组件当导入到客户端组件中,就会被认为是客户端组件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/669810.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

案例实践 | 基于长安链的首钢供应链金融科技服务平台

案例名称-首钢供应链金融科技服务平台 ■ 建设单位 首惠产业金融服务集团有限公司 ■ 用户群体 核心企业、资金方&#xff08;多为银行&#xff09;等合作方 ■ 应用成效 三大业务场景&#xff0c;共计关联29个业务节点&#xff0c;覆盖京票项目全部关键业务 案例背景…

CSS选择器的常见用法

大家好&#xff0c;本期博客整理了前端语言 CSS 中选择器的入门级常见用法&#xff0c;希望能对大家有所帮助 CSS 选择器的主要功能就是选中⻚⾯指定的标签元素&#xff0c;选中了元素&#xff0c;才可以设置元素的属性。 那么&#xff0c;css选择器有哪几种呢&#xff1f; 以…

win10修改conda环境和缓存默认路径

win10修改conda环境和缓存默认路径 conda环境和缓存的默认路径&#xff08;envs directories 和 package cache&#xff09;不一定要默认存储在用户目录&#xff0c;我们可以将他们设置到盈余空间稍大的其他目录来缓解这种空间压力&#xff0c;只要保证不同用户之间的设置不同…

OrangePi AIpro 变身 Android 打包机

主板基本信息介绍 OrangePi AIpro&#xff0c;是香橙派联合华为精心打造&#xff0c;建设人工智能新生态而设计的一款开发板&#xff0c;这次为大家分享下我上手的这款 OrangePi AIpro 8GB&#xff08;算力达8TOPS&#xff09; 的一些小小的经验。 基本参数如下&#xff1a; …

【权威出版】2024年新媒体、网络与电子商务国际会议(NMNE 2024)

2024年新媒体、网络与电子商务国际会议 2024 International Conference on New Media, Networking, and E-commerce 【1】会议简介 2024年新媒体、网络与电子商务国际会议即将召开&#xff0c;这是一次集结全球新媒体、网络与电子商务领域精英的学术盛会。 本次会议将深…

Vue3实战笔记(57)—一键换肤:在Vuetify中打造个性化主题切换体验

文章目录 前言一键换肤总结 前言 在当今追求极致用户体验的时代&#xff0c;为应用程序提供个性化的主题切换功能已经成为提升用户满意度和留存率的关键因素之一。Vuetify&#xff0c;作为基于Vue.js的流行前端框架&#xff0c;以其丰富的组件库和高度可定制性&#xff0c;为开…

牛客网刷题 | BC104 翻转金字塔图案

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 KiKi学习了循环&am…

Hadoop+Spark大数据技术 第七次作业

第七次作业 1. 简述Spark SQL使用的数据抽象DataFrame与Dataset的区别。 DataFrame: 基于 Row 对象的二维表格结构&#xff0c;类似于关系型数据库中的表。 行和列都有明确的 Schema&#xff08;模式&#xff09;&#xff0c;可以进行类型推断。 提供了丰富的操作接口&#xff…

打开C语言常用的内存函数大门(三) —— memset()函数(内含讲解用法和模拟实现)

文章目录 1. 前言2. memset函数2.1 memset函数原型2.2 memset函数参数的介绍2.3 memset函数的使用演示 3. memset函数的模拟实现4. 总结 1. 前言 哈喽&#xff0c;我们又见面了。通过前面两个内存函数(memcpy、memmove函数)讲解的锤炼后&#xff0c;对如何解析一个自己从来没有…

9. C++通过epoll+fork的方式实现高性能网络服务器

epollfork 实现高性能网络服务器 一般在服务器上&#xff0c;CPU是多核的&#xff0c;上述epoll实现方式只使用了其中的一个核&#xff0c;造成了资源的大量浪费。因此我们可以将epoll和fork结合来实现更高性能的网络服务器。 创建子进程函数–fork( ) 要了解线程我们先来了解…

Linux input输入子系统

Linux input 更多内容可以查看我的github Linux输入子系统框架 Linux输入子系统由驱动层、核心层、事件处理层三部分组成。 驱动层&#xff1a;输入设备的具体驱动程序&#xff0c;负责与具体的硬件设备进行交互&#xff0c;并将底层的硬件输入转化为统一的事件形式&#xff…

自然语言处理(NLP)—— 信息提取与文档分类

1. 初识信息提取 1.1 信息提取的基本知识 1.1.1 信息提取的概念 信息提取&#xff08;IE, Information Extraction&#xff09;是自然语言处理&#xff08;NLP&#xff09;领域的一个重要分支&#xff0c;它专注于从文档或语料库中提取结构化信息。这与信息检索&#xff08;I…

江协科技STM32学习-1 购买24Mhz采样逻辑分析仪

前言&#xff1a; 本文是根据哔哩哔哩网站上“江协科技STM32”视频的学习笔记&#xff0c;在这里会记录下江协科技STM32开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了江协科技STM32教学视频和链接中的内容。 引用&#xff1a; STM32入门教程-2023版 细致讲…

windows11家庭版、专业版、工作站版区别

windows11家庭版、专业版、工作站版区别 1、windows11家庭版和专业版的区别2、windows11家庭版和工作站版的区别 1、windows11家庭版和专业版的区别 windows11专业版需要$808 windows11专业版和家庭版功能对比 2、windows11家庭版和工作站版的区别 windows11工作站版需要$168…

Python基础教程——数据类型和变量

数据类型和变量 Python使用缩进来组织代码块,一般使用4个空格的缩进.使用#来注释一行,其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块.Python对大小写敏感. 1.1 整数 Python可以处理任意大小的整数,包括负整数,写法与数学上写法一致,例如&#xff1a;-10…

揭秘小程序商城的团购奇迹:独特模式引领盈利新纪元

在数字经济的新纪元里&#xff0c;你是否对那些不张扬却充满潜力的商业模式心生好奇&#xff1f;今天&#xff0c;我要为你揭示一种别出心裁的商业模式&#xff0c;它以其独特的魅力&#xff0c;不仅迅速吸引了大量用户的目光&#xff0c;更在短短一个月内创造了超过600万的惊人…

javascript DOM 设置样式

No.内容链接1Openlayers 【入门教程】 - 【源代码示例300】 2Leaflet 【入门教程】 - 【源代码图文示例 150】 3Cesium 【入门教程】 - 【源代码图文示例200】 4MapboxGL【入门教程】 - 【源代码图文示例150】 5前端就业宝典 【面试题详细答案 1000】 文章目录 一、直接…

EXSI虚拟机新增磁盘并将空间扩充到已有分区

这里写自定义目录标题 1、在EXSI虚拟机中新增一块磁盘配置大小2、确认新磁盘3、格式化新分区4、添加新分区到LVM5、将新增分区添加到已有分区里 1、在EXSI虚拟机中新增一块磁盘配置大小 注意事项&#xff1a; (1)需确保虚拟机已关闭活处于维护模式&#xff0c;避免数据丢失 (2…

【通信专题】I2C上拉电阻计算方法

I2C 通信总线是电子设计中常见的总线之一,由于 I2C 的硬件芯片内部为开漏输出,所以要求在外部增加一个上拉电阻,总线上拉电阻的选取受多个因素的影响,因此如何计算 I2C 总线的上拉电阻阻值成为硬件工程师在使用 I2C总统时需要关注的话题。 从本质上讲: I2C 总线电容和上升…

善听提醒遵循易经原则。世界大同只此一路。

如果说前路是一个大深坑&#xff0c;那必然是你之前做的事情做的不太好&#xff0c;当坏的时候&#xff0c;坏的结果来的时候&#xff0c;是因为你之前的行为&#xff0c;你也就不会再纠结了&#xff0c;会如何走出这个困境&#xff0c;是好的来了&#xff0c;不骄不躁&#xf…