This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/REACT/4. 进阶篇/14-Serverside Rendering.md
T
2026-04-29 22:19:51 +08:00

5.1 KiB
Raw Blame History

tags, create time
tags create time
React
Next.js
SSR
SSG
ISR
Frontend
2026-04-29 22:13

Server-Side Rendering

概述

服务端渲染(SSR)让 React 组件在服务器端预渲染为 HTML,显著改善首屏加载速度和 SEO。本文档以 Next.js App Router 为核心,介绍 SSR/SSG/ISR 的渲染策略与最佳实践。

渲染模式对比

graph TB
    subgraph "客户端渲染 CSR"
        A[HTML空白页] --> B["下载 JS Bundle"]
        B --> C["执行 React  hydration"]
        C --> D["显示内容"]
    end
    
    subgraph "服务端渲染 SSR"
        E[请求页面] --> F["服务器渲染 React → HTML"]
        F --> G["发送含内容的 HTML"]
        G --> H["客户端 hydration"]
        H --> I["交互可用"]
    end
    
    subgraph "静态生成 SSG"
        J["构建时渲染"] --> K["生成纯 HTML 文件"]
        K --> L["CDN 分发"]
    end
    
    style F fill:#4FC08D,color:#fff
    style H fill:#F5A87D,color:#000
    style J fill:#61DAFB,color:#000

三种策略决策表

策略 适用场景 数据时效性 构建参与
SSR(Server Render) 个性化页面、实时数据 ✅ 每次请求实时生成 ❌
SSG(Static Site Generation) 博客、文档、营销页 ⏱️ 构建时生成 ✅
ISR(Incremental Static Regeneration) 新闻列表、商品目录 🔄 定时后台更新 ✅(增量)

Next.js App Router 架构

// app/layout.tsx —— 根布局(所有页面共享)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh">
      <body>
        <Navbar />
        {children}
      </body>
    </html>
  );
}

// app/page.tsx —— 首页(SSR by default)
async function HomePage() {
  // ✅ 直接在组件中 await API
  const posts = await fetchPosts();
  
  return (
    <main>
      {posts.map(post => <PostCard key={post.id} post={post} />)}
    </main>
  );
}

// app/blog/[slug]/page.tsx —— 动态路由页
async function PostPage({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug);
  return <article>{post.content}</article>;
}

Streaming SSR + Suspense

sequenceDiagram
    participant Client as 浏览器
    participant Server as 服务器
    
    Client->>Server: GET /dashboard
    Server->>Server: 并行请求 user/profile/orders
    Note over Server: 每个请求可有自己的 Suspense boundary
    
    Server-->>Client: HTML: Navbar + Sidebar<br/>(立即显示,~200ms)
    
    Server-->>Client: Stream: Profile card<br/>(中等优先级,~800ms)
    
    Server-->>Client: Stream: Order history<br/>(低优先级,~1500ms)
    
    Note over Client: 用户体验:渐进式展示,而非等全部完成
// Dashboard layout(流式渲染的关键)
function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {/* 非关键 UI 用 Suspense 包裹 */}
      <Suspense fallback={<SidebarSkeleton />}>
        <Sidebar />
      </Suspense>
      
      {children}
      
      {/* 各区域独立 Suspense */}
      <Suspense fallback={<StatsSkeleton />}>
        <StatsPanel />
      </Suspense>
      <Suspense fallback={<OrdersSkeleton />}>
        <OrdersPanel />
      </Suspense>
    </>
  );
}

Data Fetching 策略

// 方案1:直接 await(默认缓存 + 共享缓存)
async function Page() {
  const data = await fetchData();  // 自动缓存,同路径请求去重
  return <div>{data}</div>;
}

// 方案2:带 revalidate 的 ISR
async function Page() {
  const data = await fetchData({ next: { revalidate: 60 } });  // 60秒后后台重新验证
  return <div>{data}</div>;
}

// 方案3:no-store(强制 SSR,不走缓存)
async function Page() {
  const data = await fetchData({ cache: "no-store" });  // 每次请求都获取最新数据
  return <div>{data}</div>;
}

// 方案4:client component 中的 fetch(使用 TanStack Query)
"use client";
function Page() {
  const { data } = useQuery({ queryKey: ["data"], queryFn: () => fetch("/api/data").then(r => r.json()) });
  return <div>{data}</div>;
}

SSR vs Client Component 边界

// server component(默认,无需声明)
async function ServerComponent() {
  // ✅ 可以直接访问数据库、API密钥、文件系统
  const db = await dbConnection.query("SELECT * FROM users");
  return <UsersList users={db} />;
}

// client component(需显式声明)
"use client";

function InteractiveChart() {
  // ✅ 可以使用 useState/useEffect/DOM API
  const [zoom, setZoom] = useState(1);
  useEffect(() => { ... }, []);
  
  return <canvas ref={canvasRef} />;
}

// Parent
function Dashboard() {
  return (
    <>
      <ServerComponent />           {/* 在服务端渲染 */}
      <InteractiveChart />          {/* 在客户端渲染 */}
    </>
  );
}

[!warning] Client → Server 通信限制

  • Client Component 无法直接调用 Server Component 的方法或 props
  • 解决方案:通过 URL 参数、cookies、或后端 API 传递数据

关联笔记