Next.js第十二章(RSC/服务端组件/客户端组件)

RSC(React Server Components)

RSC(服务器组件)是React19正式引入的一种新的组件类型,它可以在服务器端渲染,也可以在客户端渲染。

像传统的SSR他是在服务器提前把页面渲染好,然后返回给浏览器,然后进行水合,CSR则是在客户端渲染,而RSC则是吸取两方优势,分为服务器组件客户端组件

举个例子:

例如我们有一个官网的页面,上面都是静态内容,但下面留言框是需要交互的。

此时我们就可以拆分成两个组件:

  • 服务器组件: 上面都是静态内容,例如正文,标题,图片等,这类组件之所以适合在服务端执行,核心原因在于服务端渲染HTML+CSS的速度更快,生成的内容对搜索引擎完全可见,且无需客户端额外处理交互逻辑,完美匹配静态内容的需求。
tsx 复制代码
//Next.js 默认服务器组件
export default function HomePage() {
    return (
        <div>
            <h1>Home Page</h1>
        </div>
    )
}
  • 客户端组件: 下面留言框是需要交互的,例如交互功能,如点赞按钮、计数器、表单等。这类组件需要依赖浏览器DOM事件、状态管理(useState)、副作用(useEffect)等客户端能力,必须在客户端完成渲染和水合(即添加事件处理程序的过程)才能实现交互效果
tsx 复制代码
'use client' //声明这是一个客户端组件
export default function HomePage() {
    return (
        <div>
            <h1>Home Page</h1>
        </div>
    )
}

渲染(RSC Payload)

SSR模式是在服务器直接渲染成HTML页面,返回给浏览器的,而RSC他是一种特殊的紧凑的格式

json 复制代码
b2:["$","span",null,{"className":"line","children":["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":"    })"}]}]
b3:["$","span",null,{"className":"line","children":[["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":"  }"}],["$","span",null,{"style":{"color":"var(--shiki-token-punctuation)"},"children":","}],["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":" [])"}]]}]
b4:["$","span",null,{"className":"line","children":" "}]
b5:["$","span",null,{"className":"line","children":[["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":"  "}],["$","span",null,{"style":{"color":"var(--shiki-token-comment)"},"children":"// You can use `isPending` to give users feedback"}]]}]
b6:["$","span",null,{"className":"line","children":[["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":"  "}],["$","span",null,{"style":{"color":"var(--shiki-token-keyword)"},"children":"return"}],["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":" <"}],["$","span",null,{"style":{"color":"var(--shiki-token-string-expression)"},"children":"p"}],["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":">Total Views: {views}</"}],["$","span",null,{"style":{"color":"var(--shiki-token-string-expression)"},"children":"p"}],["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":">"}]]}]

那为什么这么做呢?因为我们的组件可以进行嵌套服务器组件>嵌套客户端组件>

黄色节点表示服务器组件,虚线节点表示客户端组件

在这个结构中,Next.js就会标记哪些是客户端组件并且预留好位置,但是不会进行水合。

那么Next.js发现客户端组件也会在服务器生成这个结构,那干脆直接服务器里面把客户端组件进行预渲染(不包含交互),这样我们就能快速看到数据,等他加载完成后再进行水合,所以客户端组件也会在服务器进行一次预渲染

优点

  • 将组件拆分成客户端组件和服务器组件,可以有效的减少bundle体积,因为服务器组件已经在服务器渲染好了,所以没必要打入bundle中,也就是说服务器组件所依赖的包都不会打进去,大大减少了bundle体积。

  • 局部水合,像传统的SSR同构模式, 所有的页面都要在客户端进行水合,而RSC将组件拆分出来,只会把客户端组件进行水合,避免了全量水合带来的性能损耗。

  • 流式加载,我们的HTML页面本来就支持流式加载,所以服务器组件可以边渲染边返回,提高了FCP(首次内容绘制)性能。

服务端组件(Server Components)

在默认情况下, page layout 都是服务端组件,服务端组件可以访问node.js API,包括处理数据库db。

src/app/server/page.tsx

tsx 复制代码
import fs from 'node:fs' //引入fs模块
import mysql, { RowDataPacket } from 'mysql2/promise' //操作数据库 仅供演示 非最佳实践
const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: '123456',
    database: 'catering',
})

export default async function ServerPage() {
    const [rows] = await pool.query<RowDataPacket[]>('SELECT * FROM goods')
    const data = fs.readFileSync('data.json', 'utf-8')
    const json = JSON.parse(data)
    return (
        <div>
            <h1>Server Page</h1>
            {json.age}///{json.name}///{json.city}
            <h3>mysql</h3>
            {rows.map((item: any) => (
                <div key={item.id}>{item.name}-{item.goodsPrice}</div>
            ))}
        </div>
    )
}

data.json

json 复制代码
{
    "name": "John",
    "age": 30,
    "city": "New York"
}

因为是在服务端渲染的所以日志会出现在控制台,那为什么控制台也会出现,是因为Next.js在本地开发模式方便我们调试进行的输出,后续生产环境就看不到了。

服务端组件的优点

  • 安全性: 我们在服务端组件中访问一些API秘钥,令牌等其他机密,不会暴露给客户端。
  • 体积: 因为服务端组件在服务器渲染,所以不会被打包到客户端,所以体积更小。
  • 全栈:可以在服务端组件访问数据库,文件系统等其他API,实现全栈开发。
  • FCP(首次内容绘制): 因为服务端组件是流式传输,所以边渲染边返回,提高了FCP(首次内容绘制)性能。

服务端组件的缺点

  • 交互性: 因为服务端组件在服务器渲染,所以无法访问浏览器API,所以无法进行交互。
  • hooks: useEffect useState 等hooks在服务端组件中无法使用。

JavaScript: 是由三部分组成的(ECMAScript,DOM,BOM),在服务端组件只能使用ECMAScript部分,无法访问DOMBOM

ECMAScript: 就是我们常用的对象,数组,es6+等这些东西是通用的在客户端和服务端都能用。

如果要使用以下有交互性的功能,我们需要使用客户端组件。

tsx 复制代码
import { useEffect,useState } from 'react'
export default function ServerPage() {
    const [count, setCount] = useState(0)
    useEffect(() => {
        console.log(document,window)
    }, [])
    return (
        <div>
            <h1>Server Page</h1>
            <button onClick={() => setCount(count + 1)}>点击</button>
            <p>{count}</p>
        </div>
    )
}

客户端组件(Client Components)

声明客户端组件需要在文件的顶部编写 'use client' 声明这是客户端组件,但是注意客户端组件会在服务端进行一次预渲染,所以访问document window 等API需要在useEffect中访问。

tsx 复制代码
'use client'
import { useEffect,useState } from 'react'
console.log('client')
export default function ServerPage() {
    const [count, setCount] = useState(0)
    console.log('client X')
    useEffect(() => {
        console.log(document,window)
    }, [])
    return (
        <div>
            <h1>Server Page</h1>
            <button onClick={() => setCount(count + 1)}>点击</button>
            <p>{count}</p>
        </div>
    )
}

所以我们可以看到他把useState的0预渲染了出来这样可以让用户先看到页面。

组件嵌套

服务端组件可以嵌套客户端组件,客户端只能嵌套不能嵌套服务端组件

why:因为客户端会把他所有的模块以及子组件认为是客户端组件,那此时如果服务端组件用了node.js的API,或者其他服务端操作,那就会报错,因为客户端组件无法访问这些API,故此客户端组件不能嵌套服务端组件。

server-only

随着Nodejs的发展,很多API已经可以跟浏览器共用了例如fetch,webSocket,未来Nodejs25支持localStorage等API,所以就会出现这种情况

下面这个函数可以在服务端组件使用,也可以在客户端组件使用,但有时候我们只想让他在服务端使用

ts 复制代码
export default function useTest(type:0 | 1) {
    if (type === 0) {
        return fetch('https://api.github.com')
    } else {
        return new WebSocket('wss://api.github.com')
    }
}
bash 复制代码
npm install server-only
or
yarn add server-only
or
pnpm add server-only

安装完成这个包之后,只需要在文件的顶部编写 import 'server-only' 声明即可,这样他就会在服务端执行,在客户端执行会报错。

tsx 复制代码
import 'server-only'
export default function useTest(type:0 | 1) {
    if (type === 0) {
        return fetch('https://api.github.com')
    } else {
        return new WebSocket('wss://api.github.com')
    }
}

客户端使用报错:

相关推荐
前端大卫1 小时前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘2 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare2 小时前
浅浅看一下设计模式
前端
Lee川2 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix2 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人2 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl2 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人2 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端