Skip to content

服务端渲染(SSR)与客户端渲染(CSR)

一、传统服务端渲染(SSR)

技术原理

核心流程

  1. 请求处理:用户发起页面请求,服务器接收 HTTP 请求
  2. 数据获取:服务器执行后端逻辑,从数据库或 API 获取数据
  3. 模板渲染:使用模板引擎(JSP、EJS 等)将数据注入 HTML 模板
  4. 响应返回:生成完整的 HTML 文档并返回给客户端
  5. 客户端展示:浏览器直接渲染接收到的静态 HTML

技术实现示例

java
// JSP 示例
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>商品列表</title>
</head>
<body>
    <h1>商品列表</h1>
    <%
        List<Product> products = productService.getProducts();
        for(Product product : products) {
    %>
        <div class="product">
            <h2><%= product.getName() %></h2>
            <p>价格: <%= product.getPrice() %></p>
        </div>
    <% } %>
</body>
</html>

优势分析

性能优势

  • 首屏加载快:用户无需等待 JavaScript 下载和执行
  • SEO 友好:搜索引擎爬虫直接获取完整内容
  • 低客户端要求:即使在 JavaScript 禁用情况下也能正常显示

开发优势

  • 技术栈统一:前后端使用相同语言和技术栈
  • 简单直接:开发流程直观,学习曲线平缓

局限性

用户体验问题

  • 页面刷新:每次导航都需要完整页面重载
  • 交互延迟:简单的交互也需要与服务器通信
  • 状态丢失:页面跳转时客户端状态无法保持

开发维护挑战

  • 代码耦合:业务逻辑与展示逻辑紧密绑定
  • 分工模糊:前端后端开发职责界限不清晰
  • 技术债务:随着项目扩大,维护成本指数级增长

二、现代服务端渲染(同构渲染)

架构设计

同构渲染核心概念

javascript
// 服务端渲染
// server.js
import { renderToString } from "react-dom/server";
import App from "./App";

app.get("/", (req, res) => {
  const html = renderToString(<App />);
  res.send(`
        <html>
            <head><title>同构应用</title></head>
            <body>
                <div id="root">${html}</div>
                <script src="/client.js"></script>
            </body>
        </html>
    `);
});

// 客户端注水
// client.js
import { hydrateRoot } from "react-dom/client";
import App from "./App";

hydrateRoot(document.getElementById("root"), <App />);

关键技术流程

  1. 服务端渲染阶段

    • 执行 React/Vue 组件渲染
    • 处理数据获取和状态初始化
    • 生成静态 HTML 字符串
  2. 客户端注水阶段

    • 复用服务端渲染的 DOM 结构
    • 绑定事件处理器和交互逻辑
    • 接管后续的路由和状态管理

性能优化策略

缓存机制

javascript
// 页面级缓存示例
const microCache = new Map();

app.get("*", (req, res) => {
  const cacheKey = req.url;

  // 检查缓存
  if (microCache.has(cacheKey)) {
    return res.send(microCache.get(cacheKey));
  }

  // 渲染页面
  const html = renderToString(<App />);

  // 设置缓存(2分钟过期)
  microCache.set(cacheKey, html);
  setTimeout(() => microCache.delete(cacheKey), 120000);

  res.send(html);
});

流式渲染优化

javascript
// React 18 流式渲染
import { renderToPipeableStream } from "react-dom/server";

app.use("*", (request, response) => {
  const { pipe } = renderToPipeableStream(<App />, {
    bootstrapScripts: ["/main.js"],
    onShellReady() {
      response.setHeader("content-type", "text/html");
      pipe(response);
    },
  });
});

技术挑战与解决方案

环境适配问题

javascript
// 环境检测与适配
const isServer = typeof window === "undefined";

// 条件性导入浏览器相关API
if (!isServer) {
  const { animationFrame } = require("./browser-apis");
}

// 数据获取适配
class DataFetcher {
  static async fetchData() {
    if (isServer) {
      // 服务端直接调用API
      return await database.query();
    } else {
      // 客户端通过HTTP请求
      return await fetch("/api/data").then((r) => r.json());
    }
  }
}

三、客户端渲染(CSR)

架构演进

单页面应用(SPA)架构

应用结构:
- index.html (入口文件)
- bundle.js (应用代码包)
- 路由管理 (React Router, Vue Router)
- 状态管理 (Redux, Vuex)
- API通信层 (RESTful/GraphQL)

现代 CSR 技术栈

javascript
// React SPA 示例
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import App from "./App";
import store from "./store";

const container = document.getElementById("root");
const root = createRoot(container);

root.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>
);

性能优化技术

代码分割与懒加载

javascript
// React.lazy 实现路由级代码分割
import { lazy, Suspense } from "react";

const Home = lazy(() => import("./components/Home"));
const About = lazy(() => import("./components/About"));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Suspense>
  );
}

资源预加载策略

html
<!-- 关键资源预加载 -->
<link rel="preload" href="/critical.css" as="style" />
<link rel="preload" href="/main.js" as="script" />
<link rel="prefetch" href="/about-bundle.js" as="script" />

<!-- 数据预获取 -->
<script>
  // 预获取下一页数据
  if (isHighSpeedNetwork()) {
    fetch("/api/next-page-data").then(() => {
      // 缓存数据供后续使用
    });
  }
</script>

SEO 优化方案

预渲染技术

javascript
// 使用Puppeteer进行预渲染
const puppeteer = require("puppeteer");

async function prerenderPage(url, outputPath) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // 设置用户代理为Googlebot
  await page.setUserAgent("Googlebot/2.1");

  await page.goto(url, { waitUntil: "networkidle0" });
  const html = await page.content();

  fs.writeFileSync(outputPath, html);
  await browser.close();
}

动态元标签管理

javascript
// React Helmet 示例
import { Helmet } from "react-helmet";

function ProductPage({ product }) {
  return (
    <div>
      <Helmet>
        <title>{product.name} - 我的商店</title>
        <meta name="description" content={product.description} />
        <meta property="og:title" content={product.name} />
        <meta property="og:description" content={product.description} />
      </Helmet>

      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  );
}

四、深度对比分析与选型指南

技术指标量化对比

评估维度传统 SSR现代 SSRCSR混合渲染
首屏时间100-300ms200-500ms500-2000ms300-800ms
可交互时间500-1000ms300-800ms1000-3000ms500-1200ms
SEO 支持度★★★★★★★★★★★★☆☆☆★★★★☆
开发复杂度★★☆☆☆★★★★☆★★★☆☆★★★★★
服务器成本★★☆☆☆★★★☆☆★★★★★★★★☆☆
用户体验★★☆☆☆★★★★☆★★★★★★★★★☆

场景化选型矩阵

内容型网站(新闻、博客)

  • 推荐方案:现代 SSR(Next.js/Nuxt.js)
  • 理由:SEO 需求强烈,内容更新频繁,需要良好的首屏性能
  • 技术栈:Next.js + Headless CMS + CDN

企业级应用(管理后台、ERP)

  • 推荐方案:CSR + 优化
  • 理由:交互复杂,SEO 需求低,注重用户体验
  • 技术栈:React/Vue + 状态管理 + PWA

电商平台

  • 推荐方案:混合渲染
  • 理由:商品页需要 SEO,个人中心需要丰富交互
  • 技术栈:Next.js(商品页) + SPA(用户中心)

移动端 Web 应用

  • 推荐方案:CSR + PWA
  • 理由:原生应用体验,离线功能需求
  • 技术栈:Vue/React + Service Worker + 应用壳架构

混合渲染策略

按路由差异化渲染

javascript
// Next.js 混合渲染配置
// next.config.js
module.exports = {
  async rewrites() {
    return [
      // 静态页面使用SSG
      { source: "/about", destination: "/about.html" },
      // 动态页面使用SSR
      { source: "/products/:id", destination: "/server/products/:id" },
      // API路由
      { source: "/api/:path*", destination: "/api/:path*" },
    ];
  },
};

边缘计算渲染

javascript
// Cloudflare Workers实现边缘SSR
export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // 缓存检查
    const cacheKey = url.pathname;
    const cached = await env.CACHE.get(cacheKey);

    if (cached) {
      return new Response(cached, {
        headers: { "Content-Type": "text/html" },
      });
    }

    // 边缘渲染
    const html = await renderApp(url);

    // 设置缓存
    await env.CACHE.put(cacheKey, html, { expirationTtl: 3600 });

    return new Response(html, {
      headers: { "Content-Type": "text/html" },
    });
  },
};

五、Next.js 深度解析:Page Router vs App Router

架构哲学对比

Page Router:文件即路由

设计理念:基于页面的思维模型
pages/
  index.tsx          → /
  about.tsx          → /about
  blog/
    index.tsx        → /blog
    [slug].tsx       → /blog/xxx

App Router:组件化路由

设计理念:基于组件的思维模型,支持布局共享和状态保持
app/
  layout.tsx         # 根布局
  page.tsx           → /
  about/
    page.tsx         → /about
  blog/
    layout.tsx       # 博客布局
    page.tsx         → /blog
    [slug]/
      page.tsx       → /blog/xxx

渲染模式技术细节

Page Router 渲染生命周期

javascript
// 页面级数据获取
export async function getServerSideProps(context) {
  // 每次请求时执行
  const data = await fetchData(context.params);
  return { props: { data } };
}

export default function Page({ data }) {
  // 客户端注水
  return <div>{data}</div>;
}

App Router 组件级渲染

javascript
// 服务端组件 - 零客户端bundle
async function ServerComponent() {
  const data = await fetch("https://api.example.com/data");
  return <div>{data}</div>;
}

// 客户端组件 - 支持交互
("use client");
function ClientComponent() {
  const [state, setState] = useState();
  return <button onClick={() => setState()}>点击</button>;
}

// 布局组件 - 共享UI
export default function Layout({ children }) {
  return (
    <html>
      <body>
        <nav>导航栏</nav>
        {children}
      </body>
    </html>
  );
}

性能基准测试对比

加载性能指标

场景Page RouterApp Router提升幅度
首屏加载1.2s0.8s33%
可交互时间1.8s1.1s39%
Bundle 大小120KB85KB29%
LCP 指标2.1s1.4s33%

流式渲染性能优势

javascript
// App Router流式渲染示例
export default async function Page() {
  return (
    <section>
      <Suspense fallback={<div>加载用户信息...</div>}>
        <UserProfile />
      </Suspense>

      <Suspense fallback={<div>加载商品列表...</div>}>
        <ProductList />
      </Suspense>
    </section>
  );
}

async function UserProfile() {
  // 快速返回的数据
  const user = await fetchUser();
  return <div>欢迎, {user.name}</div>;
}

async function ProductList() {
  // 慢速查询的数据
  const products = await fetchProducts();
  return products.map((product) => <div key={product.id}>{product.name}</div>);
}

迁移策略与最佳实践

渐进式迁移方案

javascript
// 步骤1:并行运行两个Router
// next.config.js
module.exports = {
  experimental: {
    appDir: true, // 启用App Router
  },
};

// 步骤2:逐步迁移页面
// app/legacy/[...slug]/page.tsx
export default function LegacyPage({ params }) {
  // 重定向到pages目录下的旧页面
  redirect(`/old/${params.slug.join("/")}`);
}

// 步骤3:新功能使用App Router

混合渲染配置

javascript
// 针对不同路由采用不同策略
export const dynamicParams = true;

export async function generateStaticParams() {
  // 静态生成高频页面
  return [{ id: "1" }, { id: "2" }];
}

export async function generateMetadata({ params }) {
  // 动态生成元数据
  const product = await fetchProduct(params.id);
  return { title: product.name };
}

export default async function Page({ params }) {
  // 服务端渲染
  const product = await fetchProduct(params.id);
  return <ProductDetail product={product} />;
}

六、未来趋势与演进方向

边缘渲染的兴起

  • 边缘计算平台:Vercel Edge Functions, Cloudflare Workers
  • 优势:更低延迟,更好的全球化部署
  • 技术栈:React Server Components + 边缘运行时

AI 驱动的渲染优化

javascript
// 智能预渲染示例
async function smartPrerender(userBehavior) {
  const prediction = await AIPredictor.predictNextPages(userBehavior);

  prediction.pages.forEach((page) => {
    // 预渲染预测页面
    prerenderPage(page.url);
  });
}

WebAssembly 在渲染中的应用

  • 高性能计算:复杂的图形渲染、数据处理
  • 跨语言能力:Rust、C++编写的组件在浏览器中运行
  • 混合渲染:Wasm + JavaScript 协同工作