Next.js SSR/SSG:路由与渲染模式深度解析

Next.js SSR/SSG:路由与渲染模式深度解析 > **版本说明**:本文基于 Next.js 14.x 和 15.x 最新版本编写,源码路径参考 `packages/next/src/` 核心模块 --- ## 📑 目录 1. [引言:渲染模式的演进](#1-引言渲染模式的演进) 2. [Next.js 路由系统架构](#2-nextjs-路由系统架构) 3. [SSR(服务器端渲染)深度解析](#3-ssr服务器端渲染深度解析) 4. [SSG(静态站点生成)深度解析](#4-ssg静态站点生成深度解析) 5. [ISR(增量静态再生成)混合模式](#5-isr增量静态再生成混合模式) 6. [渲染模式选择指南](#6-渲染模式选择指南) 7. [性能优化实战](#7-性能优化实战) 8. [总结](#8-总结) --- ## 1. 引言:渲染模式的演进 ### 1.1 Web 渲染简史 从传统的服务端渲染(PHP、JSP)到客户端渲染(SPA),再到现代的混合渲染模式,Web 开发经历了一个螺旋式上升的过程。 ```mermaid timeline title Web 渲染技术演进史 1990s : 服务端渲染 (PHP/JSP/ASP)

每次请求服务器生成完整HTML 2000s : AJAX 出现
局部动态更新,用户体验提升 2010s : 单页应用 (SPA)
React/Vue/Angular,完全客户端渲染 2015s : 同构渲染 (Universal SSR)
React SSR,首次服务器渲染,后续客户端接管 2019s : 混合渲染时代 (Next.js)
SSR/SSG/ISR 按需组合 2024s : 边缘渲染与部分渲染
RSC/Streaming/PPR ``` ### 1.2 Next.js 的核心价值 Next.js 将多种渲染模式统一到一个框架中,让开发者可以在**页面级别**选择最适合的渲染策略: | 渲染模式 | 全称 | 适用场景 | SEO友好 | 首屏速度 | |---------|------|---------|---------|----------| | **SSR** | Server-Side Rendering | 动态内容、个性化页面 | ✅ 优秀 | ⚡ 中等 | | **SSG** | Static Site Generation | 营销页面、文档、博客 | ✅ 完美 | 🚀 最快 | | **ISR** | Incremental Static Regeneration | 周期性更新内容 | ✅ 优秀 | 🚀 快 | | **CSR** | Client-Side Rendering | 高交互应用、管理后台 | ❌ 较差 | 🐢 慢 | --- ## 2. Next.js 路由系统架构 ### 2.1 路由系统演进 Next.js 14.x 引入了 App Router(基于 React Server Components),与 Pages Router 共存。 ```mermaid graph TB A[Next.js 应用] --> B[Pages Router
src/pages/] A --> C[App Router
src/app/] B --> B1[文件系统路由] B --> B2[getServerSideProps
SSR] B --> B3[getStaticProps
SSG/ISR] C --> C1[嵌套布局] C --> C2[Server Components
默认] C --> C3[Client Components
use client] C --> C4[数据获取方法
异步组件] style B fill:#e1f5ff style C fill:#fff4e1 style C2 fill:#c8e6c9 ``` ### 2.2 文件路由映射规则 **App Router 路由示例**(Next.js 14+): ``` src/app/ ├── (marketing)/ # 路由组(不影响URL) │ ├── about/ │ │ └── page.tsx → /about │ └── layout.tsx # 共享布局 ├── blog/ │ ├── [slug]/ # 动态路由 │ │ └── page.tsx → /blog/post-1 │ └── page.tsx → /blog ├── shop/ │ ├── [[...slug]]/ # 捕获所有路由(可选) │ │ └── page.tsx → /shop, /shop/a, /shop/a/b │ └── [...slug]/ # 捕获所有路由(必需) │ └── page.tsx → /shop/a, /shop/a/b (非/shop) └── page.tsx → / ``` **核心源码位置**(Next.js 14.2.x): - 路由匹配逻辑:`packages/next/src/server/app-render/app-render.tsx` - 文件系统路由解析:`packages/next/src/server/dev/parse-component-info.ts` --- ## 3. SSR(服务器端渲染)深度解析 ### 3.1 SSR 工作原理 服务器在**每次请求时**动态生成 HTML,然后发送给客户端。 ```mermaid sequenceDiagram participant User as 👤 用户浏览器 participant Server as 🖥️ Next.js 服务器 participant Data as 🗄️ 数据库/API User->>Server: 1. 请求页面 Server->>Data: 2. 获取数据(每次都请求) Data-->>Server: 3. 返回最新数据 Server->>Server: 4. 渲染 React 组件 → HTML Server-->>User: 5. 返回完整 HTML User->>User: 6. 显示内容(首屏快) User->>User: 7. 加载 JS,hydrate(可交互) ``` ### 3.2 Pages Router 中的 SSR 实现 使用 `getServerSideProps` 在每次请求时获取数据: ```typescript // src/pages/product/[id].tsx import { GetServerSideProps, GetServerSidePropsContext } from 'next' // 产品数据类型定义 interface Product { id: string name: string price: number description: string lastUpdated: string // 展示实时性 } interface ProductPageProps { product: Product timestamp: string // 服务器渲染时间 } /** * 在每次请求时在服务器端执行 * * 源码参考: * - packages/next/src/server/render.tsx (renderToHTML) * - packages/next/src/server/get-server-side-props.ts */ export const getServerSideProps: GetServerSideProps = async ( context: GetServerSidePropsContext ) => { const { id } = context.params || {} try { // 实时获取产品数据(包含库存、价格变动) const response = await fetch(`https://api.example.com/products/${id}\`, { headers: { // 可传递用户 Cookie 进行个性化请求 cookie: context.req.headers.cookie || '' } }) if (!response.ok) { return { notFound: true // 返回 404 页面 } } const product: Product = await response.json() // 可以访问请求上下文(req/res)、cookies、query 参数 return { props: { product, timestamp: new Date().toISOString() // 每次请求都会变化 }, // 可选:设置 HTTP 缓存头(CDN 缓存,但 Next.js 仍会重新渲染) // headers: { // 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=30' // } } } catch (error) { return { redirect: { destination: '/error', // 出错时重定向 permanent: false } } } } // React 组件(接收 props 进行渲染) export default function ProductPage({ product, timestamp }: ProductPageProps) { return (

{product.name}

价格:¥{product.price}

{product.description}
服务器渲染时间:{new Date(timestamp).toLocaleString('zh-CN')}
) } ``` ### 3.3 App Router 中的 SSR 实现 在 App Router 中,SSR 是**默认行为**(Server Components): ```typescript // src/app/product/[id]/page.tsx // 文件即路由,无需额外配置 import { notFound } from 'next/navigation' // 定义产品类型 interface Product { id: string name: string price: number stock: number } // 异步服务器组件(默认就是 SSR) // // 核心原理: // - packages/next/src/server/app-render/app-render.tsx // - renderToHTML() 将 React Server Components 流式渲染为 HTML export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) { // 在服务器端执行(每次请求都会运行) const { id } = await params // 直接 fetch 数据(自动去重、缓存可配置) const res = await fetch(`https://api.example.com/products/${id}\`, { // 默认缓存策略(可配置) // next: { revalidate: 0 } // 禁用缓存,纯 SSR }) if (!res.ok) { notFound() // 调用 notFound() 显示 404 页面 } const product: Product = await res.json() // 返回 JSX(在服务器端序列化为 HTML) return (

{product.name}

价格:¥{product.price}

库存:{product.stock} 件
渲染时间:{new Date().toLocaleString('zh-CN')}
) } ``` ### 3.4 SSR 核心源码分析 **渲染流程关键代码**(Next.js 14.2.x): ```typescript // packages/next/src/server/render.tsx (简化版) import { renderToReadableStream } from 'react-dom/server' /** * SSR 核心渲染函数 * * 关键步骤: * 1. 创建 React 组件树 * 2. 调用 getServerSideProps 或异步组件获取数据 * 3. 使用 renderToReadableStream 将组件流式渲染为 HTML * 4. 返回完整 HTML + hydration 数据 */ export async function renderToHTML({ pathname, query, req, res }: RenderOpts): Promise { // 1. 数据获取阶段 const props = await getServerSideProps({ req, res, query }) // 2. 渲染阶段(流式渲染) const stream = await renderToReadableStream( , { // 启用 Suspense 流式渲染 onError(error) { console.error('SSR Error:', error) } } ) // 3. 等待流完成 await stream.allReady // 4. 序列化 hydration 数据(嵌入到 HTML 中) const hydrationData = JSON.stringify(props) return { html: stream, hydrationData // 传递给客户端进行 hydrate } } ``` ### 3.5 SSR 优缺点对比 | 维度 | 优势 | 劣势 | |-----|------|------| | **SEO** | ✅ 完美,爬虫直接获取完整 HTML | - | | **首屏速度** | ⚡ 快(服务器已渲染好) | 🐢 受服务器响应时间影响 | | **数据新鲜度** | 🆕 每次请求都最新 | - | | **服务器负载** | - | 📊 高(每次请求都计算) | | **缓存难度** | - | ❌ 难(个性化内容无法 CDN 缓存) | | **开发复杂度** | ⭐ 中等 | - | --- ## 4. SSG(静态站点生成)深度解析 ### 4.1 SSG 工作原理 **构建时**(Build Time)预先生成静态 HTML 文件,部署后直接返回静态文件。 ```mermaid sequenceDiagram participant Dev as 👨‍💻 开发者 participant Build as 🔨 Next.js 构建 participant Server as 🖥️ 生产服务器 participant User as 👤 用户 Dev->>Build: 1. 运行 next build Build->>Build: 2. 执行 getStaticProps Build->>Build: 3. 生成所有路径的 HTML Build->>Server: 4. 部署静态文件 Note over Server,User: 生产环境 User->>Server: 5. 请求页面 Server-->>User: 6. 立即返回静态 HTML(毫秒级) ``` ### 4.2 Pages Router 中的 SSG 使用 `getStaticProps` + `getStaticPaths` 实现静态生成: ```typescript // src/pages/blog/[slug].tsx import { GetStaticProps, GetStaticPaths, GetStaticPropsContext } from 'next' interface BlogPost { id: string title: string content: string author: string publishedAt: string } interface BlogPageProps { post: BlogPost } /** * 构建时生成静态页面 * * 源码参考: * - packages/next/src/server/get-static-props.ts * - packages/next/src/build.ts (generateStaticPages) */ export const getStaticProps: GetStaticProps = async ( context: GetStaticPropsContext ) => { const { slug } = context.params || {} // 构建时获取文章数据(只执行一次) const res = await fetch(`https://api.example.com/blog/${slug}\`) const post: BlogPost = await res.json() return { props: { post }, // 可选:启用 ISR(增量静态再生成) revalidate: 60 // 每 60 秒允许重新生成一次 } } /** * 指定哪些路径需要预渲染 * * 构建时会为每个 path 调用 getStaticProps 生成 HTML */ export const getStaticPaths: GetStaticPaths = async () => { // 构建时获取所有文章列表 const res = await fetch('https://api.example.com/blog') const posts: BlogPost[] = await res.json() // 返回需要预渲染的路径列表 const paths = posts.map((post) => ({ params: { slug: post.id } })) return { paths, fallback: false // false=只渲染这些路径, 404=true=首次访问时生成 } } export default function BlogPage({ post }: BlogPageProps) { return (

{post.title}

作者:{post.author}
发布于:{post.publishedAt}
{post.content} ) } ``` ### 4.3 App Router 中的 SSG 在 App Router 中,SSG 通过 `force-static` 或默认缓存实现: ```typescript // src/app/blog/[slug]/page.tsx import { notFound } from 'next/navigation' interface BlogPost { id: string title: string content: string } /** * 静态生成的异步组件 * * 关键配置: * - fetch 的 next.revalidate 控制重新验证 * - Route Segment Config 控制整个页面 */ export default async function BlogPostPage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params // 构建/重新验证时获取数据 const res = await fetch(`https://api.example.com/blog/${slug}\`, { next: { revalidate: 3600 // 1 小时后允许重新生成(ISR) } }) if (!res.ok) { notFound() } const post: BlogPost = await res.json() return (

{post.title}

{post.content} ) } /** * 生成静态路径(可选) * * 源码:packages/next/src/server/app-render/generate-params.ts */ export async function generateStaticParams() { const posts = await fetch('https://api.example.com/blog').then(r => r.json()) // 返回需要预渲染的路径 return posts.map((post: BlogPost) => ({ slug: post.id })) } /** * 路由段配置(强制静态生成) * * 文件:src/app/blog/[slug]/page.tsx (同级) */ export const dynamic = 'force-static' // 强制静态生成 // export const dynamicParams = false // 禁止动态参数(只预渲染的路径可访问) ``` ### 4.4 SSG 构建流程源码分析 **构建时静态生成关键代码**(Next.js 14.2.x): ```typescript // packages/next/src/build.ts (简化版) import { renderToStaticMarkup } from 'react-dom/server' /** * 构建时静态页面生成器 * * 流程: * 1. 扫描 pages/ 目录,识别所有页面 * 2. 对每个带 getStaticProps 的页面: * a. 调用 getStaticPaths 获取路径列表 * b. 对每个路径调用 getStaticProps 获取数据 * c. 渲染 React 组件为静态 HTML * 3. 将生成的 HTML 写入 .next/static/pages/ */ export async function generateStaticPages( pages: Page[], config: BuildConfig ) { const staticPages: Record = {} for (const page of pages) { if (page.getStaticProps) { // 1. 获取路径列表 const paths = await page.getStaticPaths() // 2. 为每个路径生成 HTML for (const path of paths) { const props = await page.getStaticProps(path.params) const html = renderToStaticMarkup( ) // 3. 保存静态 HTML staticPages[path.pathname] = html } } } return staticPages } ``` ### 4.5 SSG 优缺点对比 | 维度 | 优势 | 劣势 | |-----|------|------| | **性能** | 🚀 最快(CDN 直接返回) | - | | **服务器负载** | 📉 最低(无服务器计算) | - | | **成本** | 💰 低(可纯 CDN 托管) | - | | **SEO** | ✅ 完美 | - | | **数据新鲜度** | - | ❌ 构建时数据(需重新构建) | | **动态内容** | - | ❌ 不支持实时数据 | --- ## 5. ISR(增量静态再生成)混合模式 ### 5.1 ISR 工作原理 ISR 结合了 SSG 的性能和 SSR 的灵活性:**后台自动更新**静态页面。 ```mermaid sequenceDiagram participant User as 👤 用户 participant CDN as 🌐 CDN 缓存 participant Server as 🖥️ Next.js 服务器 participant Worker as ⚙️ 后台更新任务 User->>CDN: 1. 请求页面 CDN-->>User: 2. 返回缓存的静态页面(快) Note over CDN,Worker: 检查缓存是否过期 alt 缓存过期 (revalidate 时间到) CDN->>Server: 3. 触发后台重新生成 Server->>Worker: 4. 执行 getStaticProps 获取新数据 Worker-->>CDN: 5. 返回新的 HTML(更新缓存) Note over CDN: 下一个用户获得新页面 end ``` ### 5.2 ISR 实现方式 **Pages Router**: ```typescript // src/pages/products/[id].tsx export const getStaticProps: GetStaticProps = async (context) => { const product = await fetchProduct(context.params.id) return { props: { product }, revalidate: 60 // ⭐ 关键:60 秒后允许重新生成 } } export const getStaticPaths: GetStaticPaths = async () => { const products = await fetchAllProducts() return { paths: products.map(p => ({ params: { id: p.id } })), fallback: 'blocking' // ⭐ 新路径:等待生成(而非 404) } } ``` **App Router**: ```typescript // src/app/products/[id]/page.tsx export default async function ProductPage({ params }) { const { id } = await params const product = await fetch(`https://api.example.com/products/${id}\`, { next: { revalidate: 60 // ⭐ ISR:60 秒后重新验证 } }).then(r => r.json()) return
{product.name}
} ``` ### 5.3 ISR vs SSR vs SSG 对比 | 特性 | SSG | ISR | SSR | |-----|-----|-----|-----| | **生成时机** | 构建时 | 构建时 + 后台更新 | 每次请求 | | **首屏速度** | 🚀 最快 | 🚀 快 | ⚡ 中等 | | **数据新鲜度** | ❌ 构建时数据 | ✅ 定期更新 | ✅ 实时 | | **服务器负载** | 📉 无 | 📊 低(仅后台更新) | 📈 高(每次请求) | | **适用场景** | 文档、博客 | 新闻、电商列表 | 个性化页面、实时数据 | --- ## 6. 渲染模式选择指南 ### 6.1 决策流程图 ```mermaid graph TD A[选择渲染模式] --> B{数据是否需要
实时更新?} B -->|否| C{页面是否
用户个性化?} B -->|是| F[选择 SSR] C -->|否| D{数据变化频率?} C -->|是| F D -->|几乎不变| E[选择 SSG
构建时生成] D -->|周期性变化| G[选择 ISR
后台更新] E --> H[示例: 营销页、文档] G --> I[示例: 新闻、博客] F --> J[示例: 用户中心、实时数据] style E fill:#c8e6c9 style G fill:#fff9c4 style F fill:#ffccbc ``` ### 6.2 实际应用场景矩阵 | 场景 | 推荐模式 | 理由 | 配置示例 | |-----|---------|------|---------| | **公司官网** | SSG | 内容不变,追求极致性能 | `export const dynamic = 'force-static'` | | **技术博客** | ISR | 文章定期更新,评论可动态加载 | `next: { revalidate: 3600 }` | | **电商首页** | ISR | 商品列表定时更新 | `revalidate: 300` | | **商品详情页** | SSR | 库存、价格实时变化 | 默认 Server Component | | **用户中心** | SSR | 高度个性化数据 | `getServerSideProps` 或 默认 | | **管理后台** | CSR | 交互复杂,无需 SEO | `'use client'` + 客户端数据获取 | | **API 文档** | SSG | 纯静态内容 | 构建时生成 | ### 6.3 混合渲染策略 Next.js 允许在**同一应用**中混合使用多种渲染模式: ```typescript // src/app/layout.tsx export default function RootLayout({ children }) { return ( {/* SSG:静态导航 */} {children} {/* SSG:静态页脚 */}

相关推荐
ZC跨境爬虫1 小时前
UI前端美化技能提升日志day5:从布局优化到CSS继承原理深度解析
前端·css·ui·html·状态模式
易生一世2 小时前
Kiro CLI的context详解
前端
huangql5202 小时前
CSS布局(六):Grid —— 像围棋一样布局
前端·css
谢尔登2 小时前
【Next】客户端组件和服务端组件
前端·javascript·react.js·架构
Mintopia2 小时前
合合信息蜜蜂 AI 最新资讯(2026.4.22 官方发布)
前端
Mintopia2 小时前
如何用第一性原理提升问题解决能力
前端
禅思院2 小时前
下篇:打造可观测的异步加载防御体系
前端·架构·前端框架
|晴 天|2 小时前
Vue 3 项目错误处理实战:Vue ErrorHandler、Promise 监控、用户友好提示
前端·javascript·vue.js
Cobyte2 小时前
8.响应式系统比对:手写 SolidJS 响应式系统
前端·javascript·vue.js