跳到主要内容

服务端渲染技术详解

概述

服务端渲染(Server-Side Rendering, SSR)是前端性能优化的关键技术,通过在服务器端预渲染页面内容来提升首屏加载速度、改善SEO效果和用户体验。

服务端渲染原理

SSR vs CSR对比

渲染方式对比图
┌─────────────────────────────────────────────────────────────┐
│ 客户端渲染 (CSR) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 请求HTML │ │ 加载JS │ │ 执行JS │ │ 渲染内容 │ │
│ │ (骨架页) │ │ 框架 │ │ 获取数据│ │ 显示页面 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ ↓ 总时间: 3-5秒 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 服务端渲染 (SSR) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 请求HTML │ │ 服务器 │ │ 返回 │ │ 立即 │ │
│ │ 页面 │ │ 渲染 │ │ 完整HTML│ │ 显示 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ ↓ 总时间: 0.5-1秒 │
└─────────────────────────────────────────────────────────────┘

React SSR实现

基础SSR实现

// server.js - Express服务器
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router-dom/server';
import App from './App';

const app = express();
const PORT = process.env.PORT || 3000;

// 静态文件服务
app.use(express.static('public'));

// 渲染HTML模板
function renderHTML(html) {
return `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSR应用</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<div id="root">${html}</div>
<script src="/js/bundle.js"></script>
</body>
</html>
`;
}

// 服务端渲染中间件
app.get('*', async (req, res) => {
try {
const context = {};

// 服务端渲染React组件
const appHtml = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);

// 返回完整HTML
const html = renderHTML(appHtml);
res.send(html);

} catch (error) {
console.error('SSR错误:', error);
res.status(500).send('服务器错误');
}
});

app.listen(PORT, () => {
console.log(`SSR服务器运行在端口 ${PORT}`);
});

客户端水合

// client.js - 客户端入口
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

// 水合应用
function hydrateApp() {
const root = ReactDOM.hydrateRoot(
document.getElementById('root'),
<BrowserRouter>
<App />
</BrowserRouter>
);

return root;
}

// 等待DOM加载完成后水合
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', hydrateApp);
} else {
hydrateApp();
}

Next.js SSR实现

页面级SSR

// pages/index.js - 首页SSR
import Head from 'next/head';

// 服务端数据获取
export async function getServerSideProps(context) {
try {
const { page = 1, category } = context.query;

// 服务端API调用
const response = await fetch(`${process.env.API_BASE_URL}/api/products?page=${page}&category=${category || ''}`);
const data = await response.json();

return {
props: {
products: data.products,
totalPages: data.totalPages,
currentPage: parseInt(page),
category: category || ''
}
};
} catch (error) {
console.error('数据获取失败:', error);
return {
props: {
products: [],
totalPages: 0,
currentPage: 1,
category: '',
error: '数据加载失败'
}
};
}
}

// 页面组件
export default function HomePage({ products, totalPages, currentPage, category, error }) {
if (error) {
return (
<div className="error-page">
<h1>出错了</h1>
<p>{error}</p>
</div>
);
}

return (
<>
<Head>
<title>产品列表 - 首页</title>
<meta name="description" content="浏览我们的产品目录" />
</Head>

<div className="home-page">
<h1>产品列表</h1>

<div className="products-grid">
{products.map(product => (
<div key={product.id} className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>{product.description}</p>
<span className="price">¥{product.price}</span>
</div>
))}
</div>
</div>
</>
);
}

静态生成 (SSG)

// pages/products/[id].js - 产品详情页SSG
import { useRouter } from 'next/router';
import Head from 'next/head';

// 静态路径生成
export async function getStaticPaths() {
try {
const response = await fetch(`${process.env.API_BASE_URL}/api/products/ids`);
const productIds = await response.json();

const paths = productIds.map(id => ({
params: { id: id.toString() }
}));

return {
paths,
fallback: 'blocking'
};
} catch (error) {
return {
paths: [],
fallback: 'blocking'
};
}
}

// 静态数据获取
export async function getStaticProps({ params }) {
try {
const { id } = params;
const response = await fetch(`${process.env.API_BASE_URL}/api/products/${id}`);
const product = await response.json();

if (!product) {
return { notFound: true };
}

return {
props: { product },
revalidate: 3600 // 1小时重新生成
};
} catch (error) {
return { notFound: true };
}
}

// 产品详情页组件
export default function ProductDetail({ product }) {
const router = useRouter();

if (router.isFallback) {
return <div>页面生成中...</div>;
}

return (
<>
<Head>
<title>{product.name} - 产品详情</title>
<meta name="description" content={product.description} />
</Head>

<div className="product-detail">
<h1>{product.name}</h1>
<p>{product.description}</p>
<div className="price">¥{product.price}</div>
</div>
</>
);
}

SSR性能优化

缓存策略

// cache.js - SSR缓存管理器
import NodeCache from 'node-cache';

class SSRCacheManager {
constructor() {
this.pageCache = new NodeCache({
stdTTL: 300, // 5分钟
maxKeys: 1000
});

this.dataCache = new NodeCache({
stdTTL: 600, // 10分钟
maxKeys: 5000
});
}

// 页面缓存
getPageCache(url, params = {}) {
const key = this.generateCacheKey('page', url, params);
return this.pageCache.get(key);
}

setPageCache(url, params = {}, html, ttl = 300) {
const key = this.generateCacheKey('page', url, params);
this.pageCache.set(key, html, ttl);
}

// 数据缓存
getDataCache(endpoint, params = {}) {
const key = this.generateCacheKey('data', endpoint, params);
return this.dataCache.get(key);
}

setDataCache(endpoint, params = {}, data, ttl = 600) {
const key = this.generateCacheKey('data', endpoint, params);
this.dataCache.set(key, data, ttl);
}

// 生成缓存键
generateCacheKey(type, identifier, params = {}) {
const paramsStr = Object.keys(params)
.sort()
.map(key => `${key}:${params[key]}`)
.join('|');

return `${type}:${identifier}:${paramsStr}`;
}

// 缓存统计
getCacheStats() {
return {
pageCache: {
keys: this.pageCache.keys().length,
hits: this.pageCache.getStats().hits,
misses: this.pageCache.getStats().misses
},
dataCache: {
keys: this.dataCache.keys().length,
hits: this.dataCache.getStats().hits,
misses: this.dataCache.getStats().misses
}
};
}
}

// 使用缓存管理器
const cacheManager = new SSRCacheManager();
export default cacheManager;

最佳实践

1. SSR架构设计

  • 同构应用: 服务端和客户端共享代码
  • 数据预加载: 在服务端获取必要数据
  • 缓存策略: 合理使用页面和数据缓存
  • 流式渲染: 提升首屏显示速度

2. 性能优化

  • 实现组件级缓存
  • 使用流式渲染
  • 优化数据获取
  • 合理设置缓存时间

3. SEO优化

  • 服务端渲染关键内容
  • 生成结构化数据
  • 优化meta标签
  • 实现预渲染

4. 用户体验

  • 避免内容闪烁
  • 实现渐进式加载
  • 提供加载状态指示
  • 优化交互响应

通过合理的服务端渲染策略,可以显著提升首屏加载速度,改善SEO效果,提供更好的用户体验。