文章
next js Rendering 学习笔记 01
当一个用户访问网站时,服务器会发送回一个单一的HTML页面

一旦浏览器下载了所有的JavaScript,它就会开始在计算机上生成HTML并将其注入到DOM根节点下,这时可以看到最终界面

Client-side Rending (CSR)
浏览器作为一个客户端,将React组件转换为你在屏幕上看到的内容,也就是客户端渲染
这种形式期初 加载div空页面然后js渲染页面内容 对SEO不友好,有意义的内容可能加载太慢,搜索引擎无法捕捉都它。
获取数据 渲染页面都在客户端执行是一个繁重的工作
Server-side Rending (SSR)

HTML在服务器上生成,浏览器可以快速解析并显式它,从而给我们更快的初始页面加载时间。
- 解决了SEO问题,用户看到实际的HTML内容,而不是空白屏幕或加载屏幕

该页面只有在JavaScript包下载并执行完毕后才能完全交互,这个包包括react本身以及你应用程序的代码
这个中药的阶段被成为hydration,在此期间,最初由服务器提供的静态HTML页面被激活
在hydration过程中react在浏览器中接管,并使用服务器渲染的HTML作为蓝图,在内存中重构组件,它仔细的规划了所有交互元素的位置,然后将JavaScript逻辑连接起来。
比如初始化应用程序状态、添加点击和鼠标悬停处理程序以及设置所有动态功能,以实现完整的交互用户体验。
存在问题:
- 如果一个组件需要从数据库或其他来源(如API)获取数据,这个获取过程必须在服务器开始渲染页面之前完成。这可能会延迟服务响应时间给给浏览器
- 为了成功进行hydration,进行hydration之前,所有的JavaScript代码都必须加载到客户端
- react会一次性将组件书进行hydration 也就是一旦开始hydration,他不会停止,直到整个树都完成。
为了弥补SSR以上的性能缺陷 React 18引用了 Suspense SSR架构

HTML流式加载 ,内容包裹在Suspense中,被包裹的部分会等待内容加载,但是不影响其他部分使用
如果有多个内容,则当前用户交互的区域会首先加载,比如sidenav也是suspense的 浏览页面的时候用户点了maincontent区域则优先加载maincontent区域内容
React的渲染,从客户端渲染发展到服务端渲染再到用于服务端渲染的Suspense
CSR -> SSR -> Suspense for SSR
React Server Components (RSC)
客户端组件
- 通常在客户端渲染,但也可以在服务器上渲染一次为HTML,使用户可以立即看到页面的HTML内容而不是空白屏幕
- 客户端组件可以完全访问客户端环境:use state, effects, event listener
- 需添加use client标识
服务端组件
- 代码保留在服务器上,永远不会下载到客户端
- 可以直接访问服务器资源
- 不需要下载、解析和执行JavaScript,没有hydration使你的应用加载变得交互更快
- 可以直连数据库
- 敏感数据在服务端不会外漏
- 服务器渲染会缓存结果
- SEO提升


因为使用一种特殊的JSON格式而不是HTML,React可以在保持重要UI状态完整的同时更新所有内容。
Static rendering
在服务端的渲染策略生成HTML页面,也就是HTML静态页面缓存

一个页面生成 .html .meta .rsc三个文件
页面中的导航链接页面会跟随一起预加载(prefetching),访问导航链接页面也就不会再次请求服务器
也就是连同HTML一起生成的还有RSC payloads和客户端hydration所需的JavaScript代码块,这样也可以预加载导航链接的页面 因为对应的RSC payloads和JavaScript代码块已经被预加载了,无需额外的服务器请求。
Dynamic rendering
nextjs检测到我们使用动态函数或者动态API时会自动切换到动态渲染
动态函数:
- cookies()
- headers()
- connection()
- drafMode()
- searchParams prop
- after()

打包的时候会输出 哪些路由是静态渲染 哪些是动态渲染
generateStaticParams 函数
- 与动态路由协同工作
- 在build时生成静态路由,而不是在请求时按需生成
使用[id]动态传参的页面路由 是动态渲染,因为下一个渲染id是不知道的,只有访问页面传参的时候才知道

上面产品详情页在请求时按需渲染
export async function generateStaticParams() {
return [{ id: "1" }, { id: "2" }, { id: "3" }];
}
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return <h1>Product {id} details</h1>;
}
以上generateStaticParams函数可以在build时候预先生成对应id页面HTML

嵌套动态参数路由

可以设置 dynamicParams = false 这样除了预生成的路由之外 访问其他动态参数页面 返回404页面
streaming 流式加载
配合suspense使用
// 组件
export const Product = async () => {
await new Promise((resolve) => setTimeout(resolve, 2000));
return <div>Product</div>;
};
// 组件
export const Reviews = async () => {
await new Promise((resolve) => setTimeout(resolve, 4000));
return <div>Reviews</div>;
};
// 页面
import { Suspense } from "react";
import { Product } from "@/components/product";
import { Reviews } from "@/components/reviews";
export default function ProductDetailPage() {
return (
<div>
<h1>Product detail page</h1>
<Suspense fallback={<p>Loading product details...</p>}>
<Product />
</Suspense>
<Suspense fallback={<p>Loading reviews...</p>}>
<Reviews />
</Suspense>
</div>
);
}
server-only包 在build时候即可报错
npm i server-only
使用方式
import "server-only"
Context providers
共享全局状态和逻辑
"use client";
import { createContext, useContext } from "react";
type Theme = {
colors: {
primary: string;
secondary: string;
};
};
const defaultTheme: Theme = {
colors: {
primary: "#007bff",
secondary: "#6c757d",
},
};
const ThemeContext = createContext<Theme>(defaultTheme);
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
return (
<ThemeContext.Provider value={defaultTheme}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
layout.tsx使用ThemeProvider
<html lang="en">
<ThemeProvider>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</ThemeProvider>
</html>
client-only
npm i client-only
使用方式
import "client-only"
server components可以嵌套
client components可以嵌套
client component可以嵌套在server component中
server component 不能嵌套 在client component 中因为任何嵌套在客户端组件内的组件都会自动成为客户端组件
可以将server component作为子属性传递给客户端组件
client component修改如下:
"use client"
import { useState } from "react"
export const ClientCompoentOne = ({
children,
}: {
children: React.ReactNode
}) => {
const [name, setName] = useState("Batman")
return (
<>
<h1>Client component one</h1>
{children}
</>
)
}
使用方式
import { ClientCompoentOne } from "@/components/client-component-one";
import { ServerComponentOne } from "@/components/server-component-one";
export default function InterLeavingPage() {
return (
<>
<h1>Interleaving page</h1>
<ClientCompoentOne>
<ServerComponentOne />
</ClientCompoentOne>
</>
)
}