常见的前端渲染模式

常见的前端渲染模式有 CSR(客户端渲染)、SSR(服务端渲染)、SSG(静态站点生成器)、ISR(增量静态再生)。Next.js 一个框架就可以实现 CSR、SSR、SSG、ISR 这些功能。

CSR

CSR,英文全称“Client-side Rendering”,中文翻译“客户端渲染”。顾名思义,渲染工作主要在客户端执行。

客户端渲染是指,客户在访问相对应的网站。浏览器会下把 HTML 结构下载下来,然后在去加载 JavaScript 文件。在 JavaScript 文件中可能会发送 AJAX请求、获取数据、渲染 DOM 等等。

这样做的的问题就是速度不够快。(SEO 不友好,主要是百度的爬虫,它并不会去执行 JS 脚本,不同的是 Google 的爬虫会去执行。这就是百度和 Google 的区别)。

在下载、解析、执行 JavaScript以及请求数据没有返回前,页面不会完全呈现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// pages/csr.js
import React, { useState, useEffect } from 'react'

export default function Page() {
const [data, setData] = useState(null)

useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result = await response.json()
setData(result)
}

fetchData().catch((e) => {
console.error('An error occurred while fetching the data: ', e)
})
}, [])

return <p>{data ? `Your data: ${JSON.stringify(data)}` : 'Loading...'}</p>
}

当访问 /csr的时候,渲染的 HTML 文件为:

image.png

JavaScript 获得数据后,最终更新为:

image.png

SSR

SSR,英文全称“Server-side Rendering”,中文翻译“服务端渲染”。顾名思义,渲染工作主要在服务端执行。

服务端渲染简单理解就是服务端会发送请求获取数据,然后渲染成静态的 HTML 文件,返回给用户。

虽然同样是发送请求,但通常服务端的环境(网络环境、设备性能)要好于客户端,所以最终的渲染速度(首屏加载时间)也会更快。

在 Next.js 中,想要使用 SSR 我们需要导出一个名为 getServerSideProps的 async 函数。这个函数会在每次请求的时候被调用。返回的数据会通过组件的 props 属性传递给组件。

1
2
3
4
5
6
7
8
9
10
11
// pages/ssr.js
export default function Page({ data }) {
return <p>{JSON.stringify(data)}</p>
}

export async function getServerSideProps() {
const res = await fetch(`https://jsonplaceholder.typicode.com/todos`)
const data = await res.json()

return { props: { data } }
}

效果如下:

image.png

服务端会在每次请求的时候编译 HTML 文件返回给客户端。查看 HTML,这些数据可以直接看到:

image.png

SSG

SSG,英文全称“Static Site Generation”,中文翻译“静态站点生成”。

SSG 场景适用于文档相关的网站。如 Vue 的官方文档使用的技术就是 SSG 。SSG 的特点就是服务端提前发送请求到服务器,当用户访问的时候直接将渲染过后的 HTML 返回给用户。

默认不发送请求的是,我们在 Next.js 编写的页面就是 SSG 。但如果要发送网络请求时我们要使用一个 API getStaticPropsgetStaticProps会在构建的时候被调用,并将数据通过 props 属性传递给页面。那如果要获取数据呢?这分两种情况。

单个请求 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// pages/ssg2.js
export default function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}

export async function getStaticProps() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
const posts = await res.json()
return {
props: {
posts,
},
}
}

第二种情况,是页面路径需要获取数据。这是什么意思呢?就比如数据库里有 100 篇文章,我肯定不可能自己手动定义 100 个路由,然后预渲染 100 个 HTML 吧。Next.js 提供了 getStaticPaths用于定义预渲染的路径。它需要搭配动态路由使用。

新建 /pages/post/[id].js,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// /pages/post/[id].js
export default function Blog({ post }) {
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) => ({
params: { id: String(post.id) },
}))

// { fallback: false } 意味着当访问其他路由的时候返回 404
return { paths, fallback: false }
}

export async function getStaticProps({ params }) {
// 如果路由地址为 /posts/1, params.id 为 1
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`)
const post = await res.json()

return { props: { post } }
}

其中,getStaticPathsgetStaticProps都会在构建的时候被调用,getStaticPaths 定义了哪些路径被预渲染,getStaticProps获取路径参数,请求数据传给页面。

当你执行 npm run build的时候,就会看到 post 文件下生成了一堆 HTML 文件:

ISR

ISR,英文全称“Incremental Static Regeneration”,中文翻译“增量静态再生”。

这个很难…建议参考官方小册

支持混合使用

在编写每个页面时候,我们并没有专门声明使用哪种渲染模式,Next.js 是自动判断的。所以一个 Next.js 应用里支持混合使用多种渲染模式。

当我们调用了相对应的 API ,Next.js 会自动的切换模式。且一个页面可以使用混合模式。如:页面可以是 SSG + CSR 的混合,由 SSG 提供初始的静态页面,提高首屏加载速度。CSR 动态填充内容,提供交互能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// pages/postList.js
import React, { useState } from 'react'

export default function Blog({ posts }) {
const [data, setData] = useState(posts)
return (
<>
<button onClick={async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
const posts = await res.json()
setData(posts.slice(10, 20))
}}>换一批</button>
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</>
)
}

export async function getStaticProps() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
const posts = await res.json()
return {
props: {
posts: posts.slice(0, 10),
},
}
}

初始的文章列表数据就是在构建的时候写入 HTML 里的,在点击换一批按钮的时候,则是在客户端发送请求重新渲染内容。

总结

经过本轮学习我们了解了常见的四种渲染模式,且我们讲解了在 Next.js 使用这四种模式。

参考

Next.js 开发指南