Next.js 如何实现 SSG、SSR 和 CSR
Next.js 如何实现 SSG、SSR 和 CSR
在上一篇文章中,我们全面解析了 SSR、SSG 和 CSR 三种渲染模式的概念与优劣。本篇将聚焦 Next.js 框架,通过实际代码演示如何在同一个项目中灵活运用这三种渲染策略。
一、SSG — 静态站点生成
SSG(Static Site Generation)在构建时生成 HTML,适合内容不经常变化的页面。
1. 基础用法:默认即 SSG
在 App Router 中,不使用任何动态数据的页面默认就是 SSG:
// app/about/page.tsx
export default function AboutPage() {
return (
<div>
<h1>关于我</h1>
<p>这是一个静态页面,构建时就生成了 HTML。</p>
</div>
);
}
2. 带数据的 SSG:generateStaticParams
对于动态路由,使用 generateStaticParams 在构建时预渲染所有页面:
// app/posts/[slug]/page.tsx
import { prisma } from "@/lib/db";
// 构建时生成所有文章的静态页面
export async function generateStaticParams() {
const posts = await prisma.post.findMany({
where: { published: true },
select: { slug: true },
});
return posts.map((post) => ({ slug: post.slug }));
}
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await prisma.post.findUnique({ where: { slug } });
return (
<article>
<h1>{post?.title}</h1>
<div>{post?.content}</div>
</article>
);
}
3. ISR:增量静态再生
通过 revalidate 选项实现按时间间隔重新生成:
// 每 60 秒重新验证
export const revalidate = 60;
export default async function PostListPage() {
const posts = await prisma.post.findMany({
where: { published: true },
orderBy: { publishedAt: "desc" },
});
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
二、SSR — 服务端渲染
SSR(Server-Side Rendering)在每次请求时在服务端生成 HTML。
1. 使用 dynamic = "force-dynamic"
// app/dashboard/page.tsx
export const dynamic = "force-dynamic";
export default async function DashboardPage() {
const stats = await prisma.post.aggregate({
_count: true,
_sum: { viewCount: true },
});
return (
<div>
<h1>仪表盘</h1>
<p>文章总数:{stats._count}</p>
<p>总浏览量:{stats._sum.viewCount}</p>
</div>
);
}
2. 使用 cookies() / headers() 自动触发 SSR
访问请求级 API 会自动将页面标记为动态渲染:
import { cookies } from "next/headers";
export default async function ProfilePage() {
const cookieStore = await cookies();
const token = cookieStore.get("auth-token");
if (!token) {
return <p>请先登录</p>;
}
const user = await verifyToken(token.value);
return <p>欢迎, {user.name}</p>;
}
3. 使用 searchParams
动态路由参数也会触发 SSR:
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ q?: string }>;
}) {
const { q } = await searchParams;
const results = await prisma.post.findMany({
where: {
title: { contains: q, mode: "insensitive" },
},
});
return (
<div>
<h2>搜索结果:{q}</h2>
{results.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
);
}
三、CSR — 客户端渲染
CSR(Client-Side Rendering)在浏览器端通过 JavaScript 获取数据并渲染。
1. 使用 "use client" + useEffect
"use client";
import { useState, useEffect } from "react";
interface Post {
id: number;
title: string;
slug: string;
}
export default function LivePostList() {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/posts?pageSize=5")
.then((res) => res.json())
.then((data) => {
setPosts(data.data.list);
setLoading(false);
});
}, []);
if (loading) return <p>加载中...</p>;
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
2. 使用 SWR 实现更优雅的 CSR
"use client";
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export default function RealtimeViews({ slug }: { slug: string }) {
const { data, isLoading } = useSWR(
\`/api/posts/\${slug}/views\`,
fetcher,
{ refreshInterval: 5000 } // 每 5 秒自动刷新
);
if (isLoading) return <span>--</span>;
return <span>{data?.viewCount} 次浏览</span>;
}
四、混合使用:实际项目中的最佳实践
Next.js 最强大之处在于可以在同一个项目中混合使用三种模式:
| 页面 | 渲染模式 | 原因 |
|---|---|---|
| 首页 | SSR | 需要展示最新文章,每次请求动态获取 |
| 文章详情页 | SSG + ISR | 内容相对固定,构建时生成,定期增量更新 |
| 搜索页 | SSR | 依赖用户输入的查询参数 |
| 管理后台 | CSR | 需要频繁交互,数据实时更新 |
| 关于页 | SSG | 纯静态内容,无需动态数据 |
架构示意
┌─────────────────────────────────────────┐
│ Next.js App │
├───────────┬───────────┬─────────────────┤
│ SSG │ SSR │ CSR │
│ │ │ │
│ /about │ / │ Admin Dashboard │
│ /posts/* │ /search │ Live Comments │
│ (build) │ (request) │ (browser) │
└───────────┴───────────┴─────────────────┘
五、如何判断页面的渲染模式?
运行 next build 后,终端会输出每个路由的渲染标记:
Route (app) Size First Load JS
┌ ○ /about 180 B 87 kB
├ ● /posts/[slug] 230 B 92 kB
├ ƒ / 340 B 95 kB
└ ƒ /search 280 B 90 kB
○ (Static) — SSG,构建时预渲染
● (SSG) — 使用 generateStaticParams 的 SSG
ƒ (Dynamic) — SSR,每次请求动态渲染
总结
| 特性 | SSG | SSR | CSR |
|---|---|---|---|
| 渲染时机 | 构建时 | 请求时(服务端) | 请求时(浏览器) |
| Next.js 实现 | 默认 / generateStaticParams | force-dynamic / cookies() | "use client" + fetch |
| 首次加载速度 | 最快(CDN 缓存) | 较快 | 较慢(需下载 JS) |
| SEO | 优秀 | 优秀 | 较差(需额外处理) |
| 数据新鲜度 | 构建时快照 | 实时 | 实时 |
| 适用场景 | 博客、文档、营销页 | 个性化内容、搜索 | 管理后台、实时交互 |
选择渲染模式的核心原则:能静态就静态,需动态才动态,交互部分用客户端。Next.js 的 App Router 让这一切变得自然而优雅。
▶评论
// 登录后即可参与讨论
