Next.js 服务端渲染:SSR、SSG 与 API 路由
什么是 Next.js 与渲染策略
Next.js 是一个基于 React 的全栈框架,它提供了开箱即用的服务端渲染(SSR)、静态站点生成(SSG) 以及 增量静态再生(ISR) 等能力。开发者无需手动配置 Webpack、路由及服务器逻辑,就能构建高性能的 Web 应用。
在传统 React 单页应用(SPA)中,浏览器下载一个几乎为空的 HTML 文件,再由 JavaScript 在客户端完成页面渲染。这会导致首屏白屏时间长、SEO 不友好。Next.js 则允许你在服务端完成第一次页面渲染,将完整的 HTML 直接发送给浏览器,从而解决上述问题。
Next.js 提供了三种核心的页面渲染方式:
- SSR(Server-Side Rendering):每次请求时在服务端生成页面。
- SSG(Static Site Generation):在构建时预先生成静态页面。
- ISR(Incremental Static Regeneration):在 SSG 的基础上,按需重新生成静态页面。
理解它们的差异与适用场景,是利用 Next.js 构建应用的关键。
服务端渲染(SSR)
SSR 的工作原理
当你需要实时数据(例如用户登录状态、个性化内容、数据库最新查询结果)时,SSR 是最直接的选择。每次用户通过浏览器请求页面时,Next.js 都会在服务器上执行对应页面的 getServerSideProps 函数,获取数据并渲染 React 组件为 HTML 字符串,然后将完整的 HTML 响应给客户端。
SSR 的生命周期如下:
- 浏览器发起页面请求。
- Next.js 服务器调用该页面的
getServerSideProps函数。 - 函数从外部 API、数据库或文件系统中获取所需数据。
- React 组件使用这些数据被渲染成 HTML。
- 服务器将包含完整内容的 HTML 返回给浏览器。
- 客户端浏览器接管页面(水合,hydration),使其变为可交互的 React 应用。
实现 SSR
在 Next.js 页面文件(pages 目录下,或 app 目录下使用 force-dynamic)中,你需要导出一个名为 getServerSideProps 的异步函数:
// pages/ssr-page.js
export default function SSRPage({ data }) {
return (
<div>
<h1>服务端渲染页面</h1>
<p>从服务器获取的数据:{data}</p>
</div>
);
}
export async function getServerSideProps(context) {
// context 中包含请求参数、req、res 等信息
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: {
data: data.message, // 将数据作为 props 传递给页面组件
},
};
}
关键点:
getServerSideProps在每次请求时都在服务端运行,因此它总能获取到最新的数据。- 函数的返回值必须是一个对象,其中
props属性会被传递给页面组件。 - 你可以在函数内访问请求上下文,例如 headers、cookies 等,实现权限校验。
SSR 的优缺点
优点:
- 首屏渲染完整,对 SEO 极其友好。
- 内容永远是最新的,适合高度动态的页面。
- 可以针对不同用户返回不同内容,实现个性化。
缺点:
- 每个请求都需要等待数据获取和页面渲染,TTFB(Time to First Byte)可能较高。
- 服务器负载随访问量线性增长,冷启动或高并发时性能压力较大。
- 不适合内容很少变化却承受大量访问的公共页面。
静态站点生成(SSG)
SSG 的概念
对于内容不经常变化的页面(如博客文章、文档、产品介绍页),在每次请求时重新渲染是一种资源浪费。静态站点生成(SSG) 的做法是:在构建时(next build)预先获取数据,并生成对应的静态 HTML 文件。后续当用户请求这些页面时,服务器可以直接提供这些静态文件,无需实时计算。
Next.js 在构建时会调用页面中导出的 getStaticProps 函数,获取数据并生成静态页面。
实现 SSG
// pages/blog/[slug].js
export default function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
export async function getStaticProps({ params }) {
// 在构建时获取数据,params 来自 getStaticPaths
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return {
props: {
post,
},
// 可选:设置 revalidate 可实现 ISR(见后文)
};
}
export async function getStaticPaths() {
// 指定需要预渲染的动态路径
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map((post) => ({
params: { slug: post.slug },
}));
return {
paths,
fallback: false, // 或 true / 'blocking'
};
}
关键点:
getStaticProps也返回包含props的对象,但它只在构建时调用一次(或配合revalidate进行增量更新)。- 对于包含动态路由的页面(如
[slug].js),必须同时导出getStaticPaths来告知 Next.js 哪些路径需要预渲染。 fallback选项控制如何处理未预渲染的路径:false表示返回 404;true表示客户端动态请求并缓存;'blocking'表示服务端等待生成完成后再返回页面。
SSG 的优点与限制
优点:
- 页面直接由 CDN 或静态文件服务器提供,加载速度极快,TTFB 极低。
- 几乎无需服务器资源,可承受巨大流量。
- SEO 最优,因为内容早在构建时就已经写入 HTML。
限制:
- 构建时间会随着页面数量增长而增加。
- 如果数据在构建后发生变化,用户看到的将是旧内容,除非配合 ISR 或手动重新部署。
- 不适合需要实时用户数据或高度动态的页面。
增量静态再生(ISR)
为什么需要 ISR
SSG 的痛点在于内容更新不及时。ISR 允许你在不重新构建整个站点的前提下,按需更新静态页面。你只需要在 getStaticProps 的返回值中加入一个 revalidate 字段(单位:秒)。
export async function getStaticProps() {
const res = await fetch('https://api.example.com/trending');
const data = await res.json();
return {
props: { data },
revalidate: 60, // 最多 60 秒后,下一次请求将触发后台重新生成
};
}
当页面发布后,第一个访问该页面的用户仍然会立即获得旧版本(stale),但 Next.js 会在后台触发重新生成。生成完成后,后续的用户将得到新页面,同时旧版本也会被替换。
ISR 结合了 SSG 的速度优势和数据的“接近实时性”,非常适合博客更新、电商商品页面等。
ISR 的工作流程
- 用户请求一个静态页面。
- Next.js 检查现有页面的生成时间是否已超过
revalidate秒。 - 如果未超时,直接返回缓存页面(stale)。
- 如果超时,仍然立即返回旧页面,同时在后台触发重新生成(新数据拉取、新页面构建)。
- 新页面生成后,替换缓存,后续请求返回新页面。
需要注意:revalidate 的时间是“最多等待时间”,并不是严格的定时刷新。只有当新的请求到来且缓存过期时,才会触发再生。
三种渲染策略的选择指南
| 策略 | 数据更新频率 | SEO 需求 | 示例场景 |
|---|---|---|---|
| SSR(服务端渲染) | 每次请求都实时 | 高 | 用户仪表盘、购物车、个性化内容 |
| SSG(静态生成) | 构建时固定,很少更新 | 最高 | 文档、博客(内容极少变动)、落地页 |
| ISR(增加增量再生) | 有一定更新频率,但可容忍短暂延迟 | 高 | 博客文章、商品详情页、新闻列表 |
有时你需要混合使用:首页用 SSG 或 ISR 保证速度,登录后的用户页面用 SSR 保证个性化。
API 路由:构建后端接口
Next.js 不仅负责前端页面的渲染,还能充当轻量级 API 服务器。你可以在 pages/api 目录下(或 app/api 目录下)创建 API 端点,这些路由只在服务端运行,不会打包到客户端代码中。
基本 API 路由
// pages/api/hello.js
export default function handler(req, res) {
if (req.method === 'GET') {
res.status(200).json({ message: 'Hello from Next.js API!' });
} else {
res.status(405).end(); // 方法不允许
}
}
API 路由的 handler 函数接收标准的 req(http.IncomingMessage)和 res(http.ServerResponse)对象,你可以对其进行解析并返回 JSON 数据、处理表单提交等。
与 SSR/SSG 配合使用
API 路由常被用于:
- 作为 BFF(Backend For Frontend):整合多个上游服务,为前端提供定制化数据接口。
- 隐藏第三方 API 密钥:敏感请求从服务端 API 路由发出,避免在前端暴露。
- 处理表单数据:接收前端表单提交并存入数据库或发送邮件。
在 getServerSideProps 或 getStaticProps 中,也可以直接调用本地 API 路由,但更推荐直接操作数据库或调用外部服务,因为 API 路由在服务端调用时相当于发起 HTTP 请求,会引入不必要的网络开销。
动态 API 路由
和页面类似,API 路由也支持动态参数:
// pages/api/posts/[id].js
export default function handler(req, res) {
const { id } = req.query;
// 根据 id 从数据库查询
res.status(200).json({ postId: id, title: '动态标题' });
}
API 路由的最佳实践
- 做好方法判断,仅响应支持的 HTTP 方法。
- 对输入进行验证和清洗,防止安全漏洞。
- 为大型应用考虑将路由逻辑拆分为独立的控制器和服务层。
- 利用 Next.js 的中间件(Middleware)实现权限校验、重定向等。
在 App Router(Next.js 13+)中的变化
自 Next.js 13 引入 App Router 后,原有的 pages 目录逐渐被 app 目录取代,渲染策略的实现方式也发生了变化:
getServerSideProps/getStaticProps等函数不再直接使用。- 转为基于 React Server Components 的模型。
- 在组件中直接通过
async函数获取数据,并利用fetch的cache选项控制静态/动态行为,或使用unstable_noStore、revalidate等指令。
示例(app 目录下的页面):
// app/blog/[slug]/page.js
export default async function Page({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { revalidate: 60 }, // 类似 ISR
});
const post = await res.json();
// 如果没有使用 next 选项,Next.js 默认会缓存该请求(类似 SSG)
// 使用 cache: 'no-store' 可将其变成类似 SSR
return <article>{post.content}</article>;
}
这种方式使得代码更加简洁,组件本身就是服务端组件。不过本教程的核心概念(SSR、SSG、ISR)在 App Router 下依然适用,只是实现细节有所更新。对于新项目,推荐使用 App Router;现有 Pages Router 仍可获得长期支持。
常见问题与调试技巧
-
数据未更新?
检查是否使用了getStaticProps但遗漏revalidate,或者浏览器缓存过强。尝试在开发模式下禁用缓存或使用无痕窗口测试。 -
getServerSideProps中请求超时?
确保外部 API 可达,并在函数中添加错误处理(try/catch),避免整页崩溃。 -
构建时静态页面过多导致内存溢出?
对于动态路由,使用fallback: true或'blocking'延迟生成不常用的页面,而不是在构建时生成全部页面。 -
API 路由无法访问?
检查文件名是否在pages/api目录下,且导出的是一个函数。在 App Router 中需在app/api下使用route.ts/js文件。
总结
Next.js 通过 SSR、SSG、ISR 和 API 路由为开发者提供了构建现代 Web 应用的完整工具箱。选择合适的渲染策略可以显著提升用户体验和性能:
- SSR 适合实时个性化内容。
- SSG 提供最快的页面加载和最好的扩展性,适合变化极少的公开内容。
- ISR 在两者之间取得平衡,适合需要一定时效性但又希望享受静态速度的页面。
- API 路由 让你无需额外搭建后端即可实现接口逻辑。
掌握这些核心概念后,你就可以在实际项目中根据业务需求灵活搭配,构建出高性能、用户友好的应用。