什么是 SSR(服务端渲染)?React 中如何实现?

CSR vs SSR vs SSG

方式 说明 优势 劣势
CSR 客户端渲染 交互流畅 首屏慢、SEO 差
SSR 服务端渲染 首屏快、SEO 好 服务器负载高
SSG 静态生成 最快、CDN 友好 内容固定
ISR 增量静态生成 兼顾速度和动态性 配置复杂

React SSR 基本流程

1. 服务器接收请求
2. 执行 React 组件 → 生成 HTML 字符串
3. 返回 HTML(包含初始数据)
4. 浏览器显示 HTML(首屏可见)
5. 浏览器加载 JS
6. Hydration:JS 接管页面,变成可交互

原生 React SSR

// server.js
import { renderToString } from 'react-dom/server'

app.get('/', (req, res) => {
  const data = await fetchData()
  const html = renderToString(
    <App initialData={data} />
  )
  res.send(`
    <html>
      <body>
        <div id="root">${html}</div>
        <script>window.__INITIAL_DATA__ = ${JSON.stringify(data)}</script>
        <script src="/client.js"></script>
      </body>
    </html>
  `)
})

Next.js(推荐方案)

// App Router (Next.js 13+)
// app/page.js
export default async function Page() {
  const data = await fetch('https://api.example.com/data')
  const json = await data.json()
  return <div>{json.title}</div>
}

// 动态路由
// app/posts/[id]/page.js
export default async function Post({ params }) {
  const post = await getPost(params.id)
  return <article>{post.content}</article>
}

// 客户端组件
'use client'
import { useState } from 'react'
export default function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

Hydration(水合)

// 客户端代码
import { hydrateRoot } from 'react-dom/client'

// 不是 createRoot(会重新渲染),而是 hydrateRoot(复用 DOM)
hydrateRoot(document.getElementById('root'), <App initialData={window.__INITIAL_DATA__} />)

Hydration Mismatch

如果服务端和客户端渲染结果不一致,React 会警告并重新渲染:

// ❌ 服务端和客户端可能不同
function Time() {
  return <div>{new Date().toLocaleTimeString()}</div>
}

// ✅ 先渲染占位,客户端挂载后再显示
function Time() {
  const [time, setTime] = useState(null)
  useEffect(() => {
    setTime(new Date().toLocaleTimeString())
  }, [])
  return <div>{time || 'Loading...'}</div>
}

何时用 SSR

React Server Components(RSC)

React 18 引入的服务端组件:

同分类其他题目