前端工程Technical Deep Dive
首屏优化利器 —— SSR、SSG、ISR 与流式渲染
发布时间2026/03/29
分类前端工程
预计阅读2 分钟
作者吴长龙
*
传统 SPA 的问题
01.内容
传统 SPA 的问题
javascript snippetjavascript
// 传统 SPA 渲染流程
// 1. 用户请求 HTML (≈50ms)
// 2. 返回空 HTML + JS
// 3. 浏览器下载 JS (500KB+ ≈ 2s)
// 4. JS 执行 → 请求 API (200ms)
// 5. 渲染页面 (100ms)
// 6. 用户看到内容
// Total: ≈ 3s+ (首屏)问题:用户需要等待所有 JS 下载、执行完成才能看到内容。
解决方案对比
| 方案 | 首屏 | SEO | 交互性 | 适用场景 |
|---|---|---|---|---|
| CSR | 慢 | 差 | 好 | 后台系统 |
| SSR | 快 | 好 | 中 | 动态内容 |
| SSG | 最快 | 好 | 差 | 静态页面 |
| ISR | 快 | 好 | 好 | 内容站点 |
| 混合 | 快 | 好 | 好 | 复杂应用 |
SSR (服务端渲染)
#### Next.js SSR
jsx snippetjsx
// pages/post/[id].js
export async function getServerSideProps(context) {
const { id } = context.params;
// 服务端获取数据
const post = await fetch(`https://api.example.com/posts/${id}`)
.then(res => res.json());
return {
props: { post }
};
}
export default function Post({ post }) {
return <h1>{post.title}</h1>;
}#### 渲染流程
code snippetcode
用户请求 → Node.js 服务器
→ 获取数据
→ 渲染 React 组件为 HTML
→ 返回完整 HTML
→ 浏览器直接显示
→ 同时下载 JS
→ hydration (水合)
→ 变为可交互#### Next.js App Router (默认 RSC)
jsx snippetjsx
// app/posts/[id]/page.tsx
async function PostPage({ params }) {
const { id } = params;
// 默认服务端组件,直接获取数据
const post = await getPost(id);
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}SSG (静态站点生成)
#### Next.js SSG
jsx snippetjsx
// pages/about.js
export async function getStaticProps() {
const data = await fetchData();
return {
props: { data },
revalidate: 60 // ISR: 60秒后重新生成
};
}
export default function About({ data }) {
return <div>{data.content}</div>;
}
// 或 App Router
// app/about/page.tsx (默认静态)
async function AboutPage() {
const data = await getData();
return <div>{data.content}</div>;
}#### 构建时生成
code snippetcode
构建阶段:
pages/index.html (已渲染)
pages/about.html (已渲染)
pages/posts/1.html (已渲染)
pages/posts/2.html (已渲染)
...
用户请求 → 直接返回静态 HTML#### 适用场景
- •文档网站
- •博客
- •营销页面
- •产品展示
ISR (增量静态再生成)
#### 工作原理
code snippetcode
首次访问 → 生成页面 → 缓存
↓
n 秒后再次访问 → 后台重新生成 → 更新缓存#### Next.js ISR 实现
jsx snippetjsx
// App Router
export const revalidate = 60; // 60秒重新验证
async function BlogPage() {
const posts = await getPosts();
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
// 指定路由
export const dynamicParams = true;
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map(post => ({ slug: post.slug }));
}#### On-Demand ISR
jsx snippetjsx
// 通过 API 触发重新生成
// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';
export async function POST(request) {
const body = await request.json();
if (body.secret !== process.env.REVALIDATE_SECRET) {
return Response.json({ message: 'Invalid token' }, { status: 401 });
}
revalidatePath('/posts');
revalidatePath('/posts/[slug]');
return Response.json({ revalidated: true });
}
// Webhook 触发
// CMS 发布文章 → 触发 Webhook → 重新生成流式渲染 (Streaming SSR)
#### 传统 SSR vs 流式
code snippetcode
传统 SSR:
[请求] → [获取数据] → [渲染HTML] → [返回]
↓ (慢,阻塞)
可能需要 500ms+
流式渲染:
[请求] → [流式返回] → [逐步显示]
↓
更快响应,首屏更快#### Next.js 流式
jsx snippetjsx
// App Router 自动流式
// 1. 立即返回布局
// 2. 挂起的数据在解决后流式注入
import { Suspense } from 'react';
async function PostPage({ params }) {
return (
<article>
{/* 立即渲染 */}
<h1>Post Title</h1>
{/* 流式加载 */}
<Suspense fallback={<CommentsSkeleton />}>
<Comments postId={params.id} />
</Suspense>
</article>
);
}
async function Comments({ postId }) {
const comments = await getComments(postId);
return comments.map(c => <div key={c.id}>{c.text}</div>);
}#### 完整示例
jsx snippetjsx
// app/posts/[slug]/page.tsx
import { Suspense } from 'react';
export default async function PostPage({ params }) {
return (
<main>
<article>
<PostContent slug={params.slug} />
</article>
<aside>
<Suspense fallback={<RelatedSkeleton />}>
<RelatedPosts category="tech" />
</Suspense>
<Suspense fallback={<CommentsSkeleton />}>
<CommentsSection postId={params.slug} />
</Suspense>
</aside>
</main>
);
}
async function PostContent({ slug }) {
const post = await getPost(slug);
return (
<>
<h1>{post.title}</h1>
<div>{post.content}</div>
</>
);
}混合渲染策略
jsx snippetjsx
// Next.js App Router 允许混合使用
// 1. 静态页面 (默认)
function AboutPage() { ... }
// 2. 动态页面
export const dynamic = 'force-dynamic';
function SearchPage({ searchParams }) { ... }
// 3. 按需重新验证
export const revalidate = 3600;
function BlogPage() { ... }
// 4. 运行时
export const runtime = 'nodejs';
function ApiPage() { ... }性能优化实践
#### 1. 减少首屏数据
jsx snippetjsx
// ❌ 不好:加载所有数据
async function PostPage() {
const post = await getFullPost(); // 包含正文、评论、相关...
return <Post post={post} />;
}
// ✅ 好:分步加载
async function PostPage() {
return (
<article>
<PostContent postId={id} />
<Suspense fallback={<CommentsSkeleton />}>
<Comments postId={id} />
</Suspense>
</article>
);
}#### 2. 骨架屏
jsx snippetjsx
function PostSkeleton() {
return (
<div className="skeleton">
<div className="title skeleton-item" />
<div className="content skeleton-item" />
</div>
);
}#### 3. 流式 UI
jsx snippetjsx
function Dashboard() {
return (
<div>
{/* 关键指标立即显示 */}
<StatsOverview />
{/* 次要内容流式加载 */}
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<RecentOrders />
</Suspense>
</div>
);
}总结
| 渲染模式 | 首屏时间 | 适用场景 |
|---|---|---|
| CSR | 慢 | 后台系统 |
| SSR | 中 | 需 SEO + 实时数据 |
| SSG | 快 | 静态内容 |
| ISR | 快 | 内容站点、博客 |
| 流式 | 快 | 复杂页面 |
现代前端推荐:Next.js App Router,默认静态,按需动态。