--- tags: [React, Next.js, SSR, SSG, ISR, Frontend] create time: 2026-04-29 22:13 --- # Server-Side Rendering ## 概述 服务端渲染(SSR)让 React 组件在服务器端预渲染为 HTML,显著改善首屏加载速度和 SEO。本文档以 Next.js App Router 为核心,介绍 SSR/SSG/ISR 的渲染策略与最佳实践。 ## 渲染模式对比 ```mermaid 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 架构 ```tsx // app/layout.tsx —— 根布局(所有页面共享) export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } // app/page.tsx —— 首页(SSR by default) async function HomePage() { // ✅ 直接在组件中 await API const posts = await fetchPosts(); return (
{posts.map(post => )}
); } // app/blog/[slug]/page.tsx —— 动态路由页 async function PostPage({ params }: { params: { slug: string } }) { const post = await getPostBySlug(params.slug); return
{post.content}
; } ``` ## Streaming SSR + Suspense ```mermaid 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
(立即显示,~200ms) Server-->>Client: Stream: Profile card
(中等优先级,~800ms) Server-->>Client: Stream: Order history
(低优先级,~1500ms) Note over Client: 用户体验:渐进式展示,而非等全部完成 ``` ```tsx // Dashboard layout(流式渲染的关键) function DashboardLayout({ children }: { children: React.ReactNode }) { return ( <> {/* 非关键 UI 用 Suspense 包裹 */} }> {children} {/* 各区域独立 Suspense */} }> }> ); } ``` ## Data Fetching 策略 ```tsx // 方案1:直接 await(默认缓存 + 共享缓存) async function Page() { const data = await fetchData(); // 自动缓存,同路径请求去重 return
{data}
; } // 方案2:带 revalidate 的 ISR async function Page() { const data = await fetchData({ next: { revalidate: 60 } }); // 60秒后后台重新验证 return
{data}
; } // 方案3:no-store(强制 SSR,不走缓存) async function Page() { const data = await fetchData({ cache: "no-store" }); // 每次请求都获取最新数据 return
{data}
; } // 方案4:client component 中的 fetch(使用 TanStack Query) "use client"; function Page() { const { data } = useQuery({ queryKey: ["data"], queryFn: () => fetch("/api/data").then(r => r.json()) }); return
{data}
; } ``` ## SSR vs Client Component 边界 ```tsx // server component(默认,无需声明) async function ServerComponent() { // ✅ 可以直接访问数据库、API密钥、文件系统 const db = await dbConnection.query("SELECT * FROM users"); return ; } // client component(需显式声明) "use client"; function InteractiveChart() { // ✅ 可以使用 useState/useEffect/DOM API const [zoom, setZoom] = useState(1); useEffect(() => { ... }, []); return ; } // Parent function Dashboard() { return ( <> {/* 在服务端渲染 */} {/* 在客户端渲染 */} ); } ``` > [!warning] Client → Server 通信限制 > - Client Component 无法直接调用 Server Component 的方法或 props > - 解决方案:通过 URL 参数、cookies、或后端 API 传递数据 ## 关联笔记