常见的前端渲染模式有 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
| 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 文件为:
JavaScript 获得数据后,最终更新为:
SSR
SSR,英文全称“Server-side Rendering”,中文翻译“服务端渲染”。顾名思义,渲染工作主要在服务端执行。
服务端渲染简单理解就是服务端会发送请求获取数据,然后渲染成静态的 HTML 文件,返回给用户。
虽然同样是发送请求,但通常服务端的环境(网络环境、设备性能)要好于客户端,所以最终的渲染速度(首屏加载时间)也会更快。
在 Next.js 中,想要使用 SSR 我们需要导出一个名为 getServerSideProps
的 async 函数。这个函数会在每次请求的时候被调用。返回的数据会通过组件的 props 属性传递给组件。
1 2 3 4 5 6 7 8 9 10 11
| 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 } } }
|
效果如下:
服务端会在每次请求的时候编译 HTML 文件返回给客户端。查看 HTML,这些数据可以直接看到:
SSG
SSG,英文全称“Static Site Generation”,中文翻译“静态站点生成”。
SSG 场景适用于文档相关的网站。如 Vue 的官方文档使用的技术就是 SSG 。SSG 的特点就是服务端提前发送请求到服务器,当用户访问的时候直接将渲染过后的 HTML 返回给用户。
默认不发送请求的是,我们在 Next.js 编写的页面就是 SSG 。但如果要发送网络请求时我们要使用一个 API getStaticProps
。getStaticProps
会在构建的时候被调用,并将数据通过 props 属性传递给页面。那如果要获取数据呢?这分两种情况。
单个请求 Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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
| 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) }, }))
return { paths, fallback: false } }
export async function getStaticProps({ params }) { const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`) const post = await res.json() return { props: { post } } }
|
其中,getStaticPaths
和 getStaticProps
都会在构建的时候被调用,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
| 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 开发指南