2025年05月28日6 分钟

CSR 客户端渲染:原理、流程与适用场景

深入理解 CSR(Client-Side Rendering)的工作原理,分析其优缺点和适用场景

什么是 CSR

CSR(Client-Side Rendering,客户端渲染)是指页面的 HTML 内容完全由浏览器端的 JavaScript 生成。服务器只返回一个几乎为空的 HTML 外壳和一个 JS 文件,浏览器下载并执行 JS 后,才动态生成页面内容。

React(Create React App)、Vue CLI 的默认模式就是 CSR。

CSR 的完整流程

用户请求 https://example.com
        │
        ▼
┌─────────────────┐
│    服务器返回      │  极简 HTML(几乎是空的)
│    空 HTML        │
└────────┬────────┘
         │
         ▼  浏览器收到 HTML
┌─────────────────────────────────────────┐
│ <!DOCTYPE html>                          │
│ <html>                                   │
│   <body>                                 │
│     <div id="root"></div>     ← 空白!    │
│     <script src="/bundle.js"></script>    │
│   </body>                                │
│ </html>                                  │
└────────┬────────────────────────────────┘
         │
         ▼  用户此时看到白屏 ⬜
         │
         │  下载 bundle.js(可能 200KB~2MB)
         │
         ▼  JS 下载完成
         │
         │  解析 + 执行 JavaScript
         │  React 初始化
         │  组件树渲染
         │  调用 API 获取数据
         │
         ▼  API 返回数据
         │
         │  React 用数据渲染真实 DOM
         │  插入 #root 中
         │
         ▼  用户终于看到完整页面 ✅

时间线

0ms        200ms       800ms       1500ms      2500ms
│           │           │            │           │
▼           ▼           ▼            ▼           ▼
请求发出   HTML到达    JS下载完     JS执行完      API数据到达
           白屏开始    开始解析      首次渲染       完整渲染
           (FP)       (开始执行)    (FCP)         (LCP)

           ├───── 白屏时间 ────────┤
                                  ├── 内容加载 ──┤

CSR 的代码实现

典型的入口文件

// index.html — 服务器返回的空壳
<!DOCTYPE html>
<html>
  <head>
    <title>My App</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/static/js/bundle.js"></script>
  </body>
</html>
// main.tsx — JS 入口
import { createRoot } from 'react-dom/client'
import App from './App'

const root = createRoot(document.getElementById('root')!)
root.render(<App />)
// App.tsx — 应用组件
function App() {
  const [users, setUsers] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => {
        setUsers(data)
        setLoading(false)
      })
  }, [])

  if (loading) return <Spinner />
  return <UserList users={users} />
}

路由也在客户端处理

// React Router — 所有路由都由 JS 处理
import { BrowserRouter, Routes, Route } from 'react-router-dom'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users/:id" element={<UserDetail />} />
      </Routes>
    </BrowserRouter>
  )
}
// 切换路由时不会向服务器发请求
// JS 拦截 URL 变化,直接渲染对应组件

CSR 的优缺点

优点

优点说明
交互体验流畅首次加载后,页面切换几乎是即时的(SPA)
服务器压力小服务器只需要提供静态文件和 API
前后端分离前端独立部署,后端只提供 API
部署简单静态文件可以放在 CDN 上
富交互支持适合复杂的用户交互(拖拽、实时编辑、动画)

缺点

缺点说明
白屏时间长用户要等 JS 下载+执行后才能看到内容
SEO 不友好搜索引擎爬虫可能看不到内容(只看到空 div)
首屏性能差FCP/LCP 指标较差
JS 依赖重JS 加载失败 = 页面完全不可用

SEO 问题详解

Google 爬虫请求页面:

CSR 返回的 HTML:
  <div id="root"></div>

爬虫看到的内容:空的!
  ← 虽然 Google 能执行 JS,但有延迟和不确定性
  ← 其他搜索引擎(百度)可能完全不执行 JS

适用场景

✅ 适合 CSR 的场景:
  ├── 后台管理系统(不需要 SEO)
  ├── SaaS 工具(Figma、Notion、飞书)
  ├── 内部系统(CRM、ERP)
  ├── 需要复杂交互的 Web 应用
  └── 实时数据看板

❌ 不适合 CSR 的场景:
  ├── 内容型网站(博客、新闻、文档)
  ├── 电商产品页(需要 SEO)
  ├── 营销落地页(首屏速度关键)
  └── 对首次加载性能要求高的场景

CSR 的优化方案

1. 代码分割(Code Splitting)

import { lazy, Suspense } from 'react'

// 路由级别代码分割
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  )
}
// 用户只下载当前路由需要的代码,不是整个 bundle

2. 骨架屏(Skeleton)

// 减少用户感知的白屏时间
function UserList() {
  const { data, isLoading } = useFetch('/api/users')

  if (isLoading) return <UserListSkeleton />  // 灰色占位块
  return <RealUserList data={data} />
}

3. 预渲染(Prerendering)

# 构建时为每个路由生成静态 HTML
# react-snap / prerender-spa-plugin
npx react-snap
# 输出:每个路由的完整 HTML(含内容)
# 爬虫能看到内容,用户首屏也更快

总结

CSR 是最简单的渲染模式,适合不需要 SEO、交互复杂的 Web 应用。对于内容型网站,应该考虑 SSR 或 SSG 来解决白屏和 SEO 问题。