跳到主要内容

服务端渲染 (SSR) 完整指南

服务端渲染是现代前端开发的重要技术,能够提升首屏加载速度、SEO 效果和用户体验。本指南详细介绍 SSR 的概念、实现方案和最佳实践。

📚 目录导航

基础概念

框架实践

性能优化

相关技术

SSR 基础概念

渲染模式对比

/**
* 前端渲染模式对比
*/

const renderingModes = {
// 客户端渲染 (CSR - Client Side Rendering)
csr: {
description: "在浏览器中使用JavaScript渲染页面",
process: [
"1. 浏览器下载HTML(通常很小)",
"2. 下载JavaScript bundle",
"3. JavaScript执行,渲染页面内容",
"4. 页面变为可交互状态",
],
advantages: ["开发简单", "服务器压力小", "用户交互流畅", "适合SPA应用"],
disadvantages: ["首屏加载慢", "SEO不友好", "白屏时间长", "依赖JavaScript"],
useCase: "管理后台、工具类应用",
},

// 服务端渲染 (SSR - Server Side Rendering)
ssr: {
description: "在服务器上渲染页面,返回完整HTML",
process: [
"1. 服务器接收请求",
"2. 服务器渲染完整HTML",
"3. 浏览器接收并显示HTML",
"4. 下载JavaScript进行水合(Hydration)",
"5. 页面变为可交互状态",
],
advantages: [
"首屏加载快",
"SEO友好",
"更好的用户体验",
"支持无JavaScript环境",
],
disadvantages: [
"服务器压力大",
"开发复杂度高",
"TTFB可能较长",
"状态管理复杂",
],
useCase: "电商网站、内容网站、营销页面",
},

// 静态站点生成 (SSG - Static Site Generation)
ssg: {
description: "构建时预渲染所有页面为静态HTML",
process: [
"1. 构建时获取数据",
"2. 生成静态HTML文件",
"3. 部署到CDN",
"4. 浏览器直接获取静态文件",
"5. JavaScript水合使页面可交互",
],
advantages: ["加载速度极快", "SEO友好", "服务器压力最小", "安全性高"],
disadvantages: [
"构建时间长",
"数据不能实时更新",
"不适合动态内容",
"页面数量限制",
],
useCase: "博客、文档站点、营销页面",
},

// 增量静态再生 (ISR - Incremental Static Regeneration)
isr: {
description: "结合SSG和SSR,按需重新生成静态页面",
process: [
"1. 首次访问返回预生成的静态页面",
"2. 后台检查是否需要更新",
"3. 如需更新,重新生成页面",
"4. 后续访问获取更新后的页面",
],
advantages: [
"兼具SSG和SSR优势",
"支持大规模站点",
"数据可以更新",
"性能优秀",
],
disadvantages: ["实现复杂", "缓存策略复杂", "调试困难"],
useCase: "大型电商、新闻网站",
},
};

/**
* 渲染模式选择指南
*/
const renderingModeSelection = {
// 根据应用类型选择
byApplicationType: {
内容网站: "SSG + ISR",
电商网站: "SSR + ISR",
管理后台: "CSR",
博客网站: "SSG",
新闻网站: "SSR + ISR",
营销页面: "SSG",
工具应用: "CSR",
},

// 根据需求选择
byRequirements: {
SEO要求高: "SSR 或 SSG",
首屏速度要求高: "SSR 或 SSG",
实时数据要求高: "SSR",
服务器成本敏感: "SSG",
开发效率要求高: "CSR",
用户交互复杂: "CSR + SSR混合",
},
};

SSR 核心概念

/**
* SSR 核心概念解析
*/

// 1. 同构应用 (Isomorphic/Universal Application)
const isomorphicConcept = {
definition: "同一套代码可以在服务器和客户端运行",

challenges: [
"环境差异处理",
"API调用方式不同",
"DOM操作限制",
"第三方库兼容性",
],

solutions: [
"使用环境检测",
"统一的数据获取层",
"条件渲染",
"Polyfill和适配器",
],
};

// 2. 水合 (Hydration)
const hydrationConcept = {
definition: "客户端JavaScript接管服务器渲染的HTML,使其变为可交互",

process: [
"1. 服务器渲染HTML",
"2. 浏览器显示静态HTML",
"3. 下载JavaScript bundle",
"4. React/Vue重新渲染虚拟DOM",
"5. 对比服务器HTML和客户端虚拟DOM",
"6. 绑定事件监听器",
"7. 页面变为可交互",
],

challenges: ["水合不匹配", "水合时间过长", "重复渲染", "状态同步"],

optimization: [
"减少JavaScript bundle大小",
"使用Streaming SSR",
"选择性水合",
"预加载关键资源",
],
};

// 3. 数据获取策略
const dataFetchingStrategies = {
// 服务器端数据获取
serverSide: {
timing: "页面渲染前",
methods: ["getServerSideProps", "getStaticProps", "loader"],
advantages: ["SEO友好", "首屏完整"],
disadvantages: ["增加TTFB", "服务器压力"],
},

// 客户端数据获取
clientSide: {
timing: "页面渲染后",
methods: ["useEffect", "componentDidMount", "onMounted"],
advantages: ["快速首屏", "减少服务器压力"],
disadvantages: ["SEO不友好", "加载状态"],
},

// 混合数据获取
hybrid: {
timing: "关键数据服务器端,次要数据客户端",
methods: ["关键数据SSR + 次要数据CSR"],
advantages: ["平衡性能和体验"],
disadvantages: ["实现复杂"],
},
};

Next.js 服务端渲染

Next.js 基础配置

/**
* Next.js 项目结构和配置
*/

// next.config.js
const nextConfig = {
// 实验性功能
experimental: {
appDir: true, // 启用 App Router
serverComponentsExternalPackages: ["mongoose"],
},

// 图片优化
images: {
domains: ["example.com", "cdn.example.com"],
formats: ["image/webp", "image/avif"],
},

// 重定向
async redirects() {
return [
{
source: "/old-page",
destination: "/new-page",
permanent: true,
},
];
},

// 重写
async rewrites() {
return [
{
source: "/api/:path*",
destination: "https://api.example.com/:path*",
},
];
},

// 环境变量
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY,
},

// 构建优化
compiler: {
removeConsole: process.env.NODE_ENV === "production",
},

// 输出配置
output: "standalone", // 用于Docker部署
};

module.exports = nextConfig;

/**
* 项目结构
*/
const projectStructure = {
// App Router 结构 (Next.js 13+)
appRouter: {
"app/": {
"layout.tsx": "根布局",
"page.tsx": "首页",
"loading.tsx": "加载组件",
"error.tsx": "错误组件",
"not-found.tsx": "404页面",
"global-error.tsx": "全局错误处理",

"blog/": {
"page.tsx": "博客列表页",
"layout.tsx": "博客布局",
"[slug]/": {
"page.tsx": "博客详情页",
},
},

"api/": {
"users/": {
"route.ts": "API路由",
},
},
},
},

// Pages Router 结构 (传统)
pagesRouter: {
"pages/": {
"_app.tsx": "应用入口",
"_document.tsx": "HTML文档",
"index.tsx": "首页",
"404.tsx": "404页面",
"500.tsx": "500页面",

"blog/": {
"index.tsx": "博客列表",
"[slug].tsx": "博客详情",
},

"api/": {
"users.ts": "API路由",
},
},
},
};

数据获取方法

/**
* Next.js 数据获取方法
*/

// 1. getServerSideProps - 服务器端渲染
export async function getServerSideProps(context) {
const { params, query, req, res } = context;

try {
// 获取数据
const response = await fetch(`https://api.example.com/posts/${params.id}`);
const post = await response.json();

// 检查权限
const user = await getUserFromRequest(req);
if (!user) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}

return {
props: {
post,
user,
},
};
} catch (error) {
console.error("获取数据失败:", error);

return {
notFound: true,
};
}
}

// 2. getStaticProps - 静态生成
export async function getStaticProps(context) {
const { params } = context;

try {
const response = await fetch(`https://api.example.com/posts/${params.id}`);
const post = await response.json();

return {
props: {
post,
},
// 增量静态再生
revalidate: 60, // 60秒后重新生成
};
} catch (error) {
return {
notFound: true,
};
}
}

// 3. getStaticPaths - 动态路由的静态生成
export async function getStaticPaths() {
// 获取所有文章ID
const response = await fetch("https://api.example.com/posts");
const posts = await response.json();

// 生成路径
const paths = posts.map((post) => ({
params: { id: post.id.toString() },
}));

return {
paths,
fallback: "blocking", // 或 true, false
};
}

// 4. App Router 中的数据获取
// app/blog/[slug]/page.tsx
async function getBlogPost(slug: string) {
const response = await fetch(`https://api.example.com/posts/${slug}`, {
// 缓存配置
next: {
revalidate: 3600, // 1小时后重新验证
tags: ["blog-post"], // 缓存标签
},
});

if (!response.ok) {
throw new Error("Failed to fetch blog post");
}

return response.json();
}

export default async function BlogPost({
params,
}: {
params: { slug: string },
}) {
const post = await getBlogPost(params.slug);

return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}

// 5. 客户端数据获取
import useSWR from "swr";

function Profile() {
const { data, error, isLoading } = useSWR("/api/user", fetcher);

if (error) return <div>加载失败</div>;
if (isLoading) return <div>加载中...</div>;

return <div>Hello {data.name}!</div>;
}

// 6. 混合数据获取策略
function BlogPage({ initialPosts }) {
const [page, setPage] = useState(1);

// 服务器端获取初始数据
const { data: posts } = useSWR(`/api/posts?page=${page}`, fetcher, {
fallbackData: page === 1 ? initialPosts : undefined,
});

return (
<div>
{posts.map((post) => (
<BlogCard key={post.id} post={post} />
))}
<Pagination page={page} onPageChange={setPage} />
</div>
);
}

export async function getServerSideProps() {
// 只获取第一页数据
const initialPosts = await fetchPosts(1);

return {
props: {
initialPosts,
},
};
}

性能优化

/**
* Next.js 性能优化策略
*/

// 1. 代码分割和懒加载
import dynamic from 'next/dynamic';

// 动态导入组件
const DynamicComponent = dynamic(() => import('../components/HeavyComponent'), {
loading: () => <p>加载中...</p>,
ssr: false, // 禁用服务器端渲染
});

// 动态导入库
const Chart = dynamic(() => import('react-chartjs-2'), {
ssr: false,
});

// 2. 图片优化
import Image from 'next/image';

function OptimizedImage() {
return (
<Image
src="/hero-image.jpg"
alt="Hero Image"
width={800}
height={600}
priority // 优先加载
placeholder="blur" // 模糊占位符
blurDataURL="data:image/jpeg;base64,..." // 占位符数据
/>
);
}

// 3. 字体优化
import { Inter } from 'next/font/google';

const inter = Inter({
subsets: ['latin'],
display: 'swap',
});

export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}

// 4. 缓存优化
// app/api/posts/route.ts
export async function GET() {
const posts = await fetchPosts();

return Response.json(posts, {
headers: {
'Cache-Control': 's-maxage=60, stale-while-revalidate=300',
},
});
}

// 5. 预加载优化
import Link from 'next/link';

function Navigation() {
return (
<nav>
<Link href="/about" prefetch={true}>
关于我们
</Link>
<Link href="/contact" prefetch={false}>
联系我们
</Link>
</nav>
);
}

// 6. Bundle 分析
// package.json
{
"scripts": {
"analyze": "ANALYZE=true next build"
}
}

// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer(nextConfig);

总结

服务端渲染技术为现代 Web 应用提供了更好的性能和用户体验。通过合理选择渲染策略、优化数据获取和缓存机制,可以构建出高性能的 SSR 应用。

关键要点:

  • 根据应用需求选择合适的渲染模式
  • 优化数据获取策略,平衡性能和体验
  • 合理使用缓存,减少服务器压力
  • 关注 Core Web Vitals 指标
  • 持续监控和优化性能

官方文档链接

Next.js 官方资源

Nuxt.js 官方资源

其他 SSR 框架

相关技术资源

性能监控工具