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 应用的终极形态!💪

相关推荐
Zacks_xdc1 天前
【全栈】Next.js + PostgreSQL + Vercel 实现完整登录系统(完整源码)
postgresql·全栈·next.js·登录鉴权·vercel
点正3 天前
详解TypeScript项目引用(Project References)中rootDir的坑:composite:true下为何不能指定rootDir
前端·next.js
小霖家的混江龙5 天前
仿淘宝 AI 推荐:用 Next.js 构建入门智能水果推荐 Demo
前端·人工智能·next.js
用户881586910917 天前
2026 年,你还不懂 Nex...
next.js
城南陌上9 天前
Next.js 博客终极 SEO 优化指南:从 Sitemap 到动态 OG 图片(end)
next.js
城南陌上9 天前
提升博客体验:给 Next.js 站点添加 RSS 订阅与毫秒级全局搜索(7)
next.js
hxy060110 天前
Nextjs实现Cookie、Localstorage、SessionStorage通用的方法
next.js
helloweilei15 天前
一文搞懂Nextjs中的Proxy
前端·next.js
小肚肚肚肚肚哦17 天前
Nextjs ISR 企业落地实战
next.js