Next.js 中 SSR 与 RSC 的区别:别再傻傻分不清了!

一、🤔 先来个"厨房比喻"回顾

还记得我们之前把前端开发比作餐厅吗?今天换个角度,把网站比作一道菜:

阶段 传统做法 SSR做法 RSC做法
备菜 把所有食材和菜谱(JS代码)端上桌 后厨炒好整盘菜端上桌 后厨把菜分成"预制菜"和"现炒菜"
上菜 客人自己炒(客户端执行JS) 直接吃(显示HTML) 先吃预制菜,再吃现炒菜
交互 炒菜时就能尝味道 吃完后才能点调料 预制菜不用动,现炒菜可以随时加料

这个比喻可能有点绕,别急,下面我们逐条拆解!

二、📊 SSR vs RSC:一张表看懂核心区别

对比维度 SSR(服务端渲染) RSC(React服务端组件) 关键差异解读
🎯 执行环境 每次请求时在服务器执行 在服务器执行,但结果可缓存 SSR每个请求都重新跑一遍,RSC可以复用
📦 输出产物 完整的 HTML 文档 特殊的 RSC Payload(组件树描述格式) RSC不直接输出HTML,而是输出一种中间格式
🔄 交互能力 需要 hydrate 激活 零 JS 包体积,完全无交互 RSC组件不能用 useState/useEffect
💾 数据获取 通过框架API(如 getServerSideProps 直接在组件里 await 数据库/API RSC可以直接查数据库,更直观
📈 JS 包体积 组件JS仍会发到客户端 RSC代码永不发到客户端 RSC大幅减少包体积,早期测试显示减少18-29%
🚰 流式渲染 传统SSR是"全有或全无" 天然支持流式 + Suspense RSC可以分块发送,优先显示关键内容

关键点解读

1. 关于"use client"的真相

很多初学者以为加了 'use client' 就是客户端渲染(CSR),这是误解!

在 Next.js 的 App Router 中,所有组件默认都是 RSC ,它们先在服务器上执行生成 HTML。即使你加上 'use client',这个组件依然会经历 SSR 过程,只是它的 JS 代码会被打包发送到客户端,用于后续的交互和水合(hydration)。

简单说:SSR 是"必选项",RSC 是"可选项"------SSR永远发生,RSC决定是否把JS发下去。

2. 为什么 RSC 能减少 JS 体积?

看个实际例子 :

javascript 复制代码
// ❌ 传统 React 组件(所有代码都发到客户端)
import marked from "marked"; // 35.9K
import sanitizeHtml from "sanitize-html"; // 206K

function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return <div dangerouslySetInnerHTML={{__html: html}} />;
}
javascript 复制代码
// ✅ RSC 版本(服务端组件,依赖零体积)
import marked from "marked"; // 0K(只在服务器)
import sanitizeHtml from "sanitize-html"; // 0K

function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return <div dangerouslySetInnerHTML={{__html: html}} />;
}

看到了吗?同样的代码,放在 RSC 里,这些库的代码就永远不会发给客户端! 这就是 RSC 最核心的威力 。

三、🎯 实战代码:一眼看出区别

3.1 传统 SSR(Pages Router 时代)

jsx 复制代码
// pages/products/[id].jsx (Pages Router)
export async function getServerSideProps({ params }) {
  // 1. 在服务器获取数据
  const res = await fetch(`https://api.example.com/products/${params.id}`);
  const product = await res.json();
  
  // 2. 返回 props
  return { props: { product } };
}

export default function ProductPage({ product }) {
  // 3. 这个组件代码会发送到客户端
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <button onClick={() => addToCart(product.id)}>加入购物车</button>
    </div>
  );
}

问题在哪? 整个组件的 JS(包括 addToCart 逻辑和渲染代码)都会被打包发给客户端,即使页面主要展示静态内容 。

3.2 RSC + Client Component(App Router 时代)

jsx 复制代码
// app/products/[id]/page.jsx (默认是 RSC)
import { db } from '@/lib/db';  // 直接连数据库!
import AddToCartButton from './AddToCartButton'; // 客户端组件

export default async function ProductPage({ params }) {
  // ✅ RSC:直接查数据库,代码永远留在服务器
  const product = await db.product.findUnique({
    where: { id: params.id }
  });
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      {/* 只有这个按钮的JS会发到客户端 */}
      <AddToCartButton productId={product.id} />
    </div>
  );
}
jsx 复制代码
// app/products/[id]/AddToCartButton.jsx
'use client'; // 标记为客户端组件

import { useState } from 'react';

export default function AddToCartButton({ productId }) {
  const [isAdding, setIsAdding] = useState(false);
  
  const handleClick = async () => {
    setIsAdding(true);
    await fetch('/api/cart/add', { 
      method: 'POST', 
      body: JSON.stringify({ productId }) 
    });
    setIsAdding(false);
  };
  
  return (
    <button onClick={handleClick} disabled={isAdding}>
      {isAdding ? '添加中...' : '加入购物车'}
    </button>
  );
}

RSC 版本的优势

  • 产品信息渲染的代码 0 KB 发到客户端
  • 只有 AddToCartButton 的 JS(大约 1KB)会打包发送
  • 数据获取直接在服务器完成,没有 API 层,没有 useEffect 瀑布流

四、🧩 它们是怎么配合工作的?

很多人以为 RSC 会取代 SSR,大错特错! 它们其实是"搭档"关系 :

Next.js App Router 的完整渲染流程

css 复制代码
1️⃣ 用户请求页面
    ↓
2️⃣ 服务器执行所有组件(无论是 RSC 还是 Client Component)
    ↓
3️⃣ 生成 HTML 流式返回(SSR 干的活,让首屏快)
    ↓
4️⃣ 同时生成 RSC Payload(描述组件树的结构)
    ↓
5️⃣ 浏览器接收 HTML,立即显示内容
    ↓
6️⃣ 加载客户端组件所需的 JS
    ↓
7️⃣ 水合(hydrate)客户端组件,使其可交互
    ↓
8️⃣ 后续导航时,直接用 RSC Payload 更新,无需刷新页面

关键理解

  • SSR 负责"首次展示":把页面变成 HTML 让用户第一时间看到
  • RSC 负责"后续效率":减少 JS 体积,让更新更轻量

用个比喻:SSR 是餐厅的"成品菜"(端上来就能吃),RSC 是"预制菜"(稍微加热就能吃),Client Component 是"现场烹饪"(需要等但更新鲜)。

五、✅ 什么时候用哪个?

5.1 优先用 RSC(服务端组件)

场景 原因
博客文章、新闻详情 内容静态,无需交互,0 JS 发送
商品列表、产品页 需要 SEO,数据在服务器获取
用户资料展示 读取数据库,无需客户端状态
Markdown 渲染 可以把 marked、sanitize-html 留在服务端

5.2 必须用 Client Component

场景 原因
按钮、表单、输入框 需要 onClickonChange 等事件
轮播图、选项卡 需要 useState 管理 UI 状态
实时数据更新 需要 useEffect 或 WebSocket
使用浏览器 API 需要访问 windowlocalStorage

5.3 黄金法则

尽量用 RSC,只在需要交互的地方用 Client Component

这个原则能让你的应用:

  • 🚀 首屏加载飞快(HTML 直接展示)
  • 📦 JS 包体积最小(交互部分才发代码)
  • 🔍 SEO 最佳(内容完整)
  • 💰 服务器资源最省(计算放服务器,客户端只管展示)

六、🎯 总结:一句话记住区别

概念 一句话解释
SSR(服务端渲染) 把 React 组件变成 HTML 发到浏览器,让首屏快、SEO 好
RSC(服务端组件) 让组件永远留在服务器执行,0 JS 发到客户端,大幅减小包体积

两者的关系:RSC 依赖 SSR 来生成初始 HTML,但通过把大部分组件留在服务器,让客户端只加载真正需要的交互代码 。

给初学者的实践建议

  1. 用 Next.js App Router(默认全是 RSC)
  2. 先从纯展示页面开始:博客、产品列表、文档站,直接用 RSC
  3. 遇到交互再切 Client :需要按钮、输入框时,才加 'use client'
  4. 保持"客户端隔离":尽量把交互部分拆成小组件,让大部分页面保持 RSC

最后送大家一句话:SSR 解决了"看得到",RSC 解决了"带得少"。两者结合,才是现代 React 应用的终极形态!💪

相关推荐
Patrick_Wilson4 小时前
IDE 升级重启后 Next.js dev 起不来?kill 无效的真正原因
node.js·next.js·前端工程化
竹林81819 小时前
用 wagmi v2 + Next.js 14 搞 NFT 交易市场前端:从合约调用失败到顺利上架,我踩了哪些坑
javascript·next.js
Xinghongia1 天前
手把手教你搭建一个基于 Next.js 16 + FastAPI 构建的高颜值前后端分离个人博客
next.js
四六的六2 天前
我用什么技术做了TLDR Scholar——AI论文速读产品完整技术栈拆解
大模型·个人开发·ai编程·next.js·技术干货·独立开发·ai工具
行者-全栈开发4 天前
【前端安全】CVE-2026-44578:Next.js SSRF 漏洞深度解析与修复实战指南
websocket·云原生·next.js·安全防护·vercel·cve-2026-44578·中间件绕过
轻口味7 天前
AI 时代全栈开发破局:TypeScript 生态实战,从入门到部署一站式通关
前端·mongodb·docker·ai·typescript·react·next.js
竹林8188 天前
Next.js + wagmi v2 踩坑实录:开发 NFT 交易市场时,我如何处理离线签名和链下元数据
javascript·next.js
小满zs8 天前
Next.js部署(Vercel)
前端·next.js