Next.js中服务器端渲染 (SSR) 详解:动态内容与 SEO 的完美结合
Next.js中服务器端渲染 (SSR) 详解:动态内容与 SEO 的完美结合
作者:码力无边
在上一篇文章中,我们深入探讨了静态站点生成 (SSG) 的强大之处,它通过在构建时预先生成页面,为用户提供了极致的访问速度。但现实世界是动态多变的,并非所有页面都能在构建时就确定其内容。
想象一下,一个用户的个人仪表盘,其内容取决于当前登录的是谁;或者一个电商网站的购物车页面,其商品列表因人而异。对于这类高度动态化、个性化的内容,我们需要一种能够在“最后一刻”——也就是在用户请求到达时——才生成页面的技术。这,就是服务器端渲染 (SSR) 的舞台,而 getServerSideProps
则是它的核心执行者。
getServerSideProps
:实时响应的“新闻记者”
让我们再次回顾那个“新闻记者”的比喻。getServerSideProps
的工作模式就像一位时刻待命的记者:
- 用户请求到达:浏览器向服务器发送一个特定页面的请求,例如
/dashboard
。 - 服务器执行函数:Next.js 服务器接收到请求,立刻执行该页面的
getServerSideProps
函数。这个函数可以访问请求的详细信息(如 cookies, headers)。 - 获取实时数据:在函数内部,你可以连接数据库、调用 API,获取针对当前用户或当前时间的最新数据。
- 实时渲染:Next.js 将获取到的数据作为 props 注入页面组件,并在服务器上实时渲染出完整的 HTML。
- 返回响应:服务器将这个新鲜出炉的 HTML 发送回浏览器。
这个过程确保了用户看到的永远是最新、最准确的内容。
何时应该选择 SSR?
尽管我们强调“尽可能静态化”,但 SSR 在以下场景中是不可或替代的:
- 高度个性化的内容:页面内容严重依赖于登录用户的信息。例如,用户个人资料页、订单历史、社交媒体的时间线。
- 需要访问请求对象 (Request Object):你需要根据请求的
headers
、cookies
或查询参数来决定页面内容。例如,根据用户的Accept-Language
头来决定页面的语言,或者通过 cookie 判断用户登录状态并决定是否重定向。 - 数据频繁且不可预测地变化:页面的数据变化极快,无法通过 SSG 的定时再生(ISR)来有效更新。例如,股票交易应用的实时行情页面。
一个常见的误区:有人认为只要页面有数据,就应该用 SSR。这是不对的。如果数据对于所有用户都是一样的(比如一篇博客),并且不需要每秒钟都更新,那么 SSG 配合 ISR (后续会讲) 是更好的选择,因为它性能更高。只有当数据必须是“请求级”实时的时候,才需要 SSR。
getServerSideProps
实战
让我们构建一个简单的场景:一个需要用户登录才能访问的个人资料页面。如果用户未登录,我们将他们重定向到登录页。
pages/profile.tsx
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import { parse } from 'cookie'; // 一个帮助解析 cookie 的库type User = {id: string;name: string;email: string;
};// 模拟从数据库根据用户ID获取用户信息
const fetchUserById = async (userId: string): Promise<User | null> => {// 在真实应用中,这里会进行数据库查询if (userId === '123') {return { id: '123', name: '码力无边', email: 'user@example.com' };}return null;
};// 1. 定义 getServerSideProps,它接收一个 context 对象
export const getServerSideProps: GetServerSideProps<{ user: User }> = async (context) => {const { req, res } = context; // 从 context 中获取 request 和 response 对象// 解析请求中的 cookieconst cookies = parse(req.headers.cookie || '');const userId = cookies.auth_token; // 假设我们的登录 token 存在这里if (!userId) {// 如果没有 token,说明用户未登录// 进行服务器端重定向return {redirect: {destination: '/login', // 重定向到登录页permanent: false, // false 表示这是一个临时重定向},};}const user = await fetchUserById(userId);if (!user) {// 如果 token 无效或用户不存在,也重定向到登录页return {redirect: {destination: '/login',permanent: false,},};}// 如果一切正常,将用户信息作为 props 传递给页面return {props: {user,},};
};// 2. 页面组件接收 props
function ProfilePage({ user }: InferGetServerSidePropsType<typeof getServerSideProps>) {return (<div><h1>欢迎回来, {user.name}!</h1><p>您的邮箱是: {user.email}</p></div>);
}export default ProfilePage;
context
对象详解
getServerSideProps
接收的 context
对象是一个宝库,它包含了所有关于当前请求的信息:
req
: Node.js 的http.IncomingMessage
对象,包含了请求头、cookies 等。res
: Node.js 的http.ServerResponse
对象,可以用来设置响应头。params
: 如果是动态路由,这里会包含路由参数(如[id]
)。query
: URL 的查询字符串部分,以对象形式表示。resolvedUrl
: 请求的完整 URL 路径(不含域名)。locale
,locales
: 与国际化 (i18n) 相关的信息。
SSR 的性能考量
SSR 虽然强大,但它是有成本的。每次请求都需要服务器进行计算,这被称为 TTFB (Time to First Byte) 的开销。如果你的 getServerSideProps
函数执行缓慢(例如,调用了一个很慢的 API),那么用户将会看到一个更长的加载等待时间。
优化建议:
- 确保数据源快速:你的数据库查询或 API 调用必须足够快。
- 使用缓存:对于一些可以短时间缓存的数据,考虑在服务器端引入缓存层(如 Redis),避免每次都重复计算或请求。
- 谨慎使用:再次强调,不要滥用 SSR。问问自己:“这个页面的数据真的需要在每次请求时都是最新的吗?”如果答案是否定的,请考虑 SSG 或 ISR。
总结:SSG vs. SSR
我们现在已经深入了解了 Next.js 的两种核心预渲染策略。让我们用一张最终的对比图来巩固记忆:
对比维度 | getStaticProps (SSG) | getServerSideProps (SSR) |
---|---|---|
核心理念 | 构建时一次性生成 | 每次请求实时生成 |
性能 | ⚡️ 极快 (CDN 边缘分发) | ✅ 较快 (服务器实时计算) |
适用场景 | 博客、文档、营销页 (内容对所有人一致) | 个人中心、购物车 (内容高度个性化、实时) |
数据新鲜度 | 构建时的快照 | 绝对实时 |
SEO | 💯 完美 | 💯 完美 |
开发中刷新 | 数据只在构建时获取 | 每次刷新页面都重新获取数据 |
理解 SSG 和 SSR 的权衡是成为一名高效 Next.js 开发者的关键。它们不是竞争关系,而是互补的工具。一个复杂的应用通常会混合使用这两种模式:用 SSG 构建大部分公开页面以获得最佳性能,用 SSR 来处理需要登录和个性化的私有页面。
我们的工具箱里现在有了两个强大的工具。但如果我想要一个既有 SSG 的速度,又能像 SSR 一样定期更新内容的“两全其美”的方案呢?下一篇文章,我们将揭晓 Next.js 的又一个创新功能:增量静态再生 (ISR),它将打破静态与动态的界限。敬请期待!