Skip to content

React 服务端渲染深度

1. 渲染模式对比

模式全称渲染时机适用场景
CSRClient-Side Rendering浏览器运行时后台管理、SPA
SSRServer-Side Rendering每次请求时动态内容、SEO
SSGStatic Site Generation构建时博客、文档
ISRIncremental Static Regen构建时 + 按需更新电商、新闻

2. SSR 原理

传统 SSR 流程

Request → renderToString() → HTML String → Response

浏览器接收 HTML → hydrate() → 可交互

问题: TTFB 慢

renderToString 必须渲染完整个组件树才能响应。


3. Streaming SSR (React 18)

核心 API

javascript
import { renderToPipeableStream } from 'react-dom/server';

app.get('/', (req, res) => {
    const stream = renderToPipeableStream(
        <App />,
        {
            bootstrapScripts: ['/main.js'],
            onShellReady() {
                // Shell (Suspense 外的内容) 准备好
                res.setHeader('Content-Type', 'text/html');
                stream.pipe(res);
            },
            onAllReady() {
                // 所有内容 (含 Suspense 内) 准备好
            }
        }
    );
});

工作原理

  1. Shell 先发送: Suspense 外的内容立即发送
  2. 流式发送: Suspense 内容准备好后,通过 <script> 注入
  3. Selective Hydration: 优先 hydrate 用户交互的部分

HTML 输出示例

html
<!-- 初始 Shell -->
<div id="root">
    <header>...</header>
    <Suspense fallback="Loading...">
        <!--$?--><template id="B:0"></template><!--/$-->
    </Suspense>
</div>

<!-- 流式注入 (稍后) -->
<script>
    $RC = function(a, b) { /* 替换内容 */ };
    $RC("B:0", "<div>Loaded Content</div>");
</script>

4. React Server Components (RSC)

核心思想

  • Server Component: 只在服务端运行,不发送 JS 到客户端
  • Client Component: 传统 React 组件 ('use client')

约束

Server ComponentClient Component
使用 useState/useEffect
使用浏览器 API
直接访问数据库/文件系统
发送 JS 到客户端
可 import Client Component
可 import Server Component

示例

jsx
// app/page.tsx (Server Component by default)
async function Page() {
    // 直接访问数据库!
    const posts = await db.query('SELECT * FROM posts');
    
    return (
        <div>
            {posts.map(post => (
                <Article key={post.id} post={post} />
            ))}
            {/* Client Component 处理交互 */}
            <LikeButton />
        </div>
    );
}
jsx
// components/LikeButton.tsx
'use client';

export function LikeButton() {
    const [liked, setLiked] = useState(false);
    return <button onClick={() => setLiked(true)}>Like</button>;
}

优势

  1. 减少 Bundle Size: Server Component 不发送 JS
  2. 直接访问后端资源: 无需 API 层
  3. 自动代码分割: 按组件边界分割

5. Hydration Mismatch 问题

常见原因

  • 服务端渲染时间 (new Date()) 与客户端不同
  • 使用 window 对象 (服务端不存在)
  • 第三方库不兼容 SSR

解决方案

jsx
// 1. 使用 useEffect 延迟客户端渲染
function ClientOnlyComponent() {
    const [mounted, setMounted] = useState(false);
    useEffect(() => setMounted(true), []);
    if (!mounted) return null;
    return <div>{window.innerWidth}</div>;
}

// 2. suppressHydrationWarning (不推荐)
<time suppressHydrationWarning>{new Date().toISOString()}</time>

前端面试知识库