🍂落页
登 录

Next.js 笔记

  • 安装及项目结构
  • 路由
  • 获取数据
  • 渲染
  • 缓存
  • 优化
🍂落页
TALAXY
渲染
🍂落页
TALAXY

渲染

渲染
  • 服务端组件
  • 客户端组件
  • 两端搭配
  • Edge 和 Node.js 运行时
  • 参考

应用的渲染可以有两种环境:客户端和服务端。两者都有自己的特性和限制,不能说孰优孰劣。

在 Web 开发中,可以用 网络边界 的概念来划分这两种环境。可以用 React 的 "use client" 来定义网络边界。或者用 "use server" 来告诉 React 在服务器上做一些计算性的工作。

服务端组件

React 服务端组件可以让你在服务器上进行 UI 渲染及缓存。在 Next.js 上,渲染工作则会根据路由片段的划分来启用流式渲染或部分渲染。

有三种服务端渲染的策略:静态渲染、动态渲染、流式渲染。

Next.js 默认使用服务端组件,无需额外配置。

服务端渲染的优势

  • 数据获取 - 服务器通常离数据资源更近,缩短了请求时间。也可以减少客户端请求的数量。
  • 安全 - 服务端组件会将敏感数据和逻辑留存在服务端上,比如 token 或 API Key ,避免暴露给客户端。
  • 缓存 - 服务端会对渲染结果进行缓存,一定程度上减少了渲染的工作量和请求次数。
  • 打包体积 - 一些大的依赖可以留存在服务端,来减少客户端包体积。
  • 首屏加载时间 - 服务端可以生成 HTML ,让用户立即看到页面。
  • SEO - 渲染的 HTML 可以让搜索引擎或社交网站的爬虫索引你的页面。
  • 流式渲染 - 服务端组件允许你将渲染工作进行拆分,并逐步渲染传输给客户端,确保用户能立即看到页面的一部分。

渲染步骤

Next.js 使用 React 的 API 进行服务端渲染。渲染工作会根据路由片段和 Suspense 进行拆分。

每一拆分的块的渲染步骤如下:

  1. 在服务端上:
    1. React 将服务端组件渲染为一种特殊的数据格式,称为 RSC Payload(React Server Component Payload)。
    2. Next.js 拿着 RSC Payload 和客户端组件的 JS 代码来渲染 HTML 。
  2. 接着,在客户端上:
    1. 会立即加载一个无法交互的 HTML ,即首屏页面。
    2. RSC Payload 会比对客户端和服务端的组件树,来更新 DOM 。
    3. 根据 JS 代码来 hydrate 客户端组件,使应用可交互。

RSC Payload 通常会包含这些信息:

  • 服务端组件的渲染结果;
  • 客户端组件的占位符及其 JS 文件的引用地址;
  • 所有服务端组件传给客户端组件的参数。

服务端渲染策略

静态渲染

Next.js 默认采取静态渲染。路由会在构建时(或当数据重新校验后)被渲染好。渲染结果会被缓存并且可以推送到 CDN 上。

静态渲染通常用于与用户数据不相关的页面,比如静态博客页面。

动态渲染

当页面与用户数据相关,或当请求中带有信息时(比如 URL 参数、cookie 等),通常使用动态渲染。在动态渲染中,路由会对每次请求都进行渲染。

路由并非只能是纯静态或纯动态。在 Next.js 中,RSC Payload 和请求数据是分别缓存的。

当面对一个动态函数或者未缓存数据请求时,Next.js 会对整个路由进行动态渲染。这些都是自动的,开发者不需要手动设置静态或动态渲染。

动态函数 会依赖一些在请求时才可获知的信息,比如用户 cookie、请求头、查询参数。在 Next.js 中具体有:

  • cookies() 和 headers() - 会直接将路由切为动态渲染;
  • useSearchParams() - 若在客户端组件中使用了这一函数,会跳过静态渲染,并回退到最近的 Suspense 上。因此建议将这类组件包裹在 Suspense 中,来保证上层组件可以正常静态渲染;
  • searchParams - 使用该页面属性会将路由切为动态渲染。

流式渲染

Next.js 默认开启了流式渲染,可以用 loading.js 及 Suspense 启用这一特性。

客户端组件

在 Next.js 中需在文件最顶部加上 "use client" 来将组件声明为客户端组件。如果在服务端组件中使用了客户端才可用的 API(比如 useState、onClick 等),会报错。

使用客户端组件的好处有:

  • 交互 - 可以使用 state、effect、事件监听等提供用户反馈的 API ;
  • 浏览器 API - 比如 localStorage 等。

渲染方式

不同的页面请求方式,客户端组件的渲染方式不同。请求方式有 整页加载 和 导航切换 。

整页加载

整页加载,即首次打开页面或者采用了浏览器刷新。为了优化首屏加载,Next.js(借助 React)均会将客户端和服务端组件渲染为 HTML ,无需等待客户端组件的 JS 包。

导航切换

通过导航切换打开的页面,客户端组件会完全由客户端渲染。客户端拿到客户端组件的 JS 包后,React 会根据 RSC Payload 来比对客户端和服务端组件树,然后更新 DOM 。

两端搭配

服务端组件注意点

  • 传递数据 - 在服务端不可以利用 React Context 来传递参数,但可以利用 fetch 或 React 中的缓存机制来获取数据。更多见 React 中的 记忆化 。

  • 防止服务端代码进入客户端 - 为了防止服务端代码(比如接口请求)意外地在客户端代码中使用,可以引入 server-only 库(需要单独安装依赖):

    import 'server-only'
    
    export async function getData() {
        const res = await fetch('https://external-service.com/data', {
            headers: {
                authorization: process.env.API_KEY,
            },
        })
        
        return res.json()
    }
    
  • 第三方库引入 - 许多第三方库可能没有在组件上声明 "use client" ,需要我们自行封装一下:

    'use client'
    
    import { Carousel } from 'acme-carousel'
    
    export default Carousel
    
  • Context - 在服务端代码中使用 Context 时,该 Context 应当在一个单独的文件中声明,并声明为客户端代码:

    'use client'
    
    import { createContext } from 'react'
    
    export const ThemeContext = createContext({})
    
    export default function ThemeProvider({ children }) {
        return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
    }
    

客户端组件注意点

  • 下沉客户端组件代码 - 为了减少 JS 代码包,应当将组件树中的存在交互逻辑的客户端组件单独拆分出来,使得其余部分尽可能成为服务端组件。
  • 从服务端传递给客户端的参数应当可序列化 。

两端代码嵌套

在客户端代码中,依然可以嵌套服务端代码,但有一些注意点:

  • 当你的服务端代码换为客户端代码时,客户端如果要获取数据或资源是需要单独向服务器请求的。
  • 当服务端接收到请求时,所有服务端组件(包括内嵌在客户端组件的服务端组件)均会被最先渲染。渲染结果(RSC Payload)会包含客户端组件的位置信息。在客户端上,React 会根据 RSC Payload 来将服务端和客户端组件合为一棵树。
  • 因为客户端组件会在服务端组件之后渲染,客户端组件代码中不能包含对服务端代码的 import 导入。服务端组件应当作为参数传递给客户端:
    import ClientComponent from './client-component'
    import ServerComponent from './server-component'
    
    export default function Page() {
        return (
            <ClientComponent>
                {/* 该组件依然会在服务端中预先渲染 */}
                <ServerComponent />
            </ClientComponent>
        )
    }
    

Edge 和 Node.js 运行时

TODO

参考

Rendering - nextjs.org

TALAXY 落于 2023年12月19日 。

Press space bar to start a drag. When dragging you can use the arrow keys to move the item around and escape to cancel. Some screen readers may require you to be in focus mode or to use your pass through key