前言
大家好,我是鲫小鱼。是一名不写前端代码
的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!
第七章:服务端渲染 (SSR) 深度探究:getServerSideProps
一、理论讲解
1.1 什么是服务端渲染(SSR)?
服务端渲染(Server Side Rendering,简称 SSR)是指在服务器端生成 HTML 内容并返回给客户端浏览器的技术。与传统的客户端渲染(CSR)相比,SSR 可以显著提升首屏加载速度、SEO 友好性和内容可访问性。
- SSR 优势 :
- 首屏渲染更快,用户体验更好
- 更利于 SEO,爬虫可直接抓取内容
- 适合内容型、营销型、首屏要求高的应用
- SSR 劣势 :
- 服务端压力大,扩展性需关注
- 复杂交互仍需客户端 JS 支持
1.2 Next.js SSR 的实现方式
Next.js 提供了多种数据获取方式:
getStaticProps
(静态生成,SSG)getServerSideProps
(服务端渲染,SSR)getInitialProps
(早期方案,已不推荐)
本章重点聚焦于 getServerSideProps
,它让我们可以在每次请求时都在服务端动态获取数据。
1.3 SSR 适用场景
- 需要实时数据的页面(如用户仪表盘、新闻列表)
- 需要权限校验的页面
- SEO 要求高的内容页
- 需要根据请求头、Cookie、Session 动态渲染的页面
二、getServerSideProps
详解
2.1 基本用法
getServerSideProps
是 Next.js Pages Router 下的服务端数据获取函数。它只能用于 pages
目录下的页面组件,并且每次请求都会在服务端执行。
tsx
// pages/news.tsx
import React from 'react';
import type { GetServerSideProps, NextPage } from 'next';
interface NewsProps {
newsList: { id: number; title: string; content: string }[];
}
const News: NextPage<NewsProps> = ({ newsList }) => (
<div>
<h1>最新新闻</h1>
<ul>
{newsList.map((item) => (
<li key={item.id}>
<h2>{item.title}</h2>
<p>{item.content}</p>
</li>
))}
</ul>
</div>
);
export const getServerSideProps: GetServerSideProps = async (context) => {
// 这里可以访问 context.req, context.res, query, params, cookies 等
const res = await fetch('https://api.example.com/news');
const newsList = await res.json();
return {
props: { newsList },
};
};
export default News;
2.2 参数详解
context.req
/context.res
:Node.js 原生请求/响应对象context.query
:URL 查询参数context.params
:动态路由参数context.locale
:当前语言环境context.resolvedUrl
:解析后的 URL
2.3 返回值说明
props
:传递给页面组件的 propsredirect
:重定向到其他页面notFound
:返回 404 页面
ts
return {
redirect: {
destination: '/login',
permanent: false,
},
};
三、实战项目:SSR 新闻列表
3.1 需求分析
- 实现一个新闻列表页,数据实时获取,支持移动端适配
- 未登录用户跳转到登录页
- 支持国际化(中英文切换)
- 错误处理与性能优化
3.2 目录结构
bash
/pages
├── news.tsx
├── login.tsx
└── _app.tsx
/components
└── NewsList.tsx
/utils
└── auth.ts
3.3 代码实现
3.3.1 登录校验工具
ts
// utils/auth.ts
export function checkLogin(req: any) {
// 假设通过 Cookie 判断登录
return Boolean(req.cookies?.token);
}
3.3.2 SSR 页面实现
tsx
// pages/news.tsx
import React from 'react';
import { GetServerSideProps } from 'next';
import { checkLogin } from '../utils/auth';
import NewsList from '../components/NewsList';
const NewsPage = ({ newsList, locale }: any) => (
<div>
<h1>{locale === 'en' ? 'Latest News' : '最新新闻'}</h1>
<NewsList newsList={newsList} />
</div>
);
export const getServerSideProps: GetServerSideProps = async (context) => {
const { req, locale } = context;
if (!checkLogin(req)) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
try {
const res = await fetch('https://api.example.com/news');
const newsList = await res.json();
return {
props: { newsList, locale },
};
} catch (error) {
return {
props: { newsList: [], locale },
};
}
};
export default NewsPage;
3.3.3 组件拆分与样式
tsx
// components/NewsList.tsx
import React from 'react';
export default function NewsList({ newsList }: { newsList: any[] }) {
if (!newsList.length) return <div>暂无新闻</div>;
return (
<ul>
{newsList.map((item) => (
<li key={item.id}>
<h2>{item.title}</h2>
<p>{item.content}</p>
</li>
))}
</ul>
);
}
3.3.4 移动端适配与样式优化
css
/* styles/news.module.css */
ul {
padding: 0;
list-style: none;
}
li {
margin-bottom: 16px;
border-bottom: 1px solid #eee;
padding-bottom: 8px;
}
@media (max-width: 600px) {
h2 {
font-size: 18px;
}
p {
font-size: 14px;
}
}
3.3.5 国际化支持
Next.js 支持内置国际化,可通过 context.locale
获取当前语言。
3.3.6 错误处理与性能优化
- 使用 try/catch 捕获服务端异常,避免页面崩溃
- 可结合 SWR、React Query 等客户端缓存方案提升体验
- 服务端可设置缓存头(Cache-Control)优化接口性能
四、企业级最佳实践
4.1 代码组织与团队协作
4.1.1 目录结构与分层
在企业级项目中,推荐采用分层结构:
pages/
只负责路由和页面入口,页面只做数据拼装和渲染components/
存放通用 UI 组件modules/
或features/
目录按业务模块拆分,聚合相关页面、组件、hooks、serviceservices/
统一管理 API 请求、服务端数据获取逻辑utils/
存放工具函数middlewares/
存放 SSR 中间件(如鉴权、国际化、日志等)
4.1.2 SSR 中间件模式
可将 SSR 逻辑拆分为中间件链式调用,提升复用性和可维护性。
ts
// middlewares/withAuth.ts
import { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
export function withAuth<T>(gssp: (ctx: GetServerSidePropsContext) => Promise<GetServerSidePropsResult<T>>) {
return async (ctx: GetServerSidePropsContext) => {
const { req } = ctx;
if (!req.cookies?.token) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
return await gssp(ctx);
};
}
ts
// pages/dashboard.tsx
import { withAuth } from '../middlewares/withAuth';
export const getServerSideProps = withAuth(async (ctx) => {
// 业务数据获取
return { props: { /* ... */ } };
});
4.1.3 团队协作与代码规范
- 统一 TypeScript 类型定义,提升开发效率和可维护性
- 结合 ESLint、Prettier、Husky 保障代码风格一致
- 采用 Git Flow 或 trunk-based workflow 管理分支
- 代码评审流程自动化(如 GitHub Actions)
4.2 安全与权限
4.2.1 SSR 鉴权与权限控制
- 在
getServerSideProps
中校验用户身份、角色权限 - 敏感接口仅在服务端调用,避免暴露 token、密钥
- 对于多角色系统,结合 RBAC(基于角色的访问控制)
ts
// middlewares/withRole.ts
export function withRole(roles: string[], gssp) {
return async (ctx) => {
const user = getUserFromCookie(ctx.req);
if (!user || !roles.includes(user.role)) {
return { redirect: { destination: '/403', permanent: false } };
}
return await gssp(ctx);
};
}
4.2.2 防御 SSR 注入与 XSS
- 服务端渲染时,所有输出内容需转义
- 严格校验和过滤外部输入,防止 SQL 注入、命令注入
- 使用 Helmet 等库设置 HTTP 安全头
4.2.3 CSRF 防护
- SSR 场景下,表单/接口需校验 CSRF Token
- 可结合 NextAuth.js、csrf 包实现
4.3 性能优化
4.3.1 SSR 缓存与分布式
- 利用 HTTP 缓存头(Cache-Control)提升页面响应速度
- 结合 Redis、Memcached 等缓存热点数据,减轻数据库压力
- CDN 边缘缓存 SSR 页面(如 Vercel Edge、Cloudflare)
ts
// pages/news.tsx
export const getServerSideProps = async (ctx) => {
ctx.res.setHeader('Cache-Control', 'public, s-maxage=60, stale-while-revalidate=30');
// ...数据获取
return { props: { /* ... */ } };
};
4.3.2 数据预取与并发优化
- 服务端可并发请求多个数据源,提升整体渲染速度
- 合理拆分页面,避免 SSR 过重
ts
// 并发获取多接口数据
const [userRes, newsRes] = await Promise.all([
fetch('https://api.example.com/user'),
fetch('https://api.example.com/news'),
]);
4.3.3 图片与静态资源优化
- 使用 next/image 组件自动优化图片
- 静态资源走 CDN,减少主站压力
4.3.4 SSR 性能监控
- 接入 APM(如 Datadog、NewRelic)监控 SSR 响应时间
- 记录慢查询、异常日志,定期分析优化
4.4 移动端适配
4.4.1 响应式设计
- 使用 CSS-in-JS、Tailwind CSS、styled-components 等方案实现响应式
- SSR 阶段可根据 UA 判断设备类型,动态渲染不同布局
ts
// 判断移动端 UA
const isMobile = /mobile/i.test(ctx.req.headers['user-agent'] || '');
4.4.2 移动端首屏优化
- SSR 阶段输出精简 HTML,减少首屏体积
- 关键 CSS 内联,提升渲染速度
4.5 自动化测试与监控
4.5.1 SSR 单元测试
- 使用 Jest + Testing Library 测试 SSR 逻辑
- Mock fetch、数据库等外部依赖
ts
// __tests__/news.test.ts
import { getServerSideProps } from '../pages/news';
test('SSR 正常返回数据', async () => {
global.fetch = jest.fn(() => Promise.resolve({ json: () => [{ id: 1, title: 'test', content: 'c' }] })) as any;
const ctx = { req: { cookies: { token: '123' } }, locale: 'zh' } as any;
const result = await getServerSideProps(ctx);
expect(result).toHaveProperty('props.newsList');
});
4.5.2 E2E 测试
- 使用 Playwright/Cypress 模拟用户端到端访问,验证 SSR 页面渲染、跳转、权限
4.5.3 监控与告警
- 接入 Sentry、LogRocket 等监控 SSR 错误
- SSR 关键路径设置告警,及时发现问题
4.6 常见问题与解决方案
4.6.1 SSR 页面数据闪烁/丢失
- 客户端 hydration 需与服务端数据一致,避免"内容闪烁"
- 建议 SSR 返回的数据结构与客户端保持一致
4.6.2 SEO 优化细节
- SSR 页面需设置完整 meta、title、OG 标签
- 动态内容建议服务端渲染,提升爬虫抓取率
4.6.3 SSR 性能瓶颈与扩展
- 高并发场景下,建议前置缓存、分布式部署
- SSR 服务可横向扩展,结合负载均衡
4.6.4 SSR 与客户端状态同步
- SSR 获取的数据通过 props 传递,客户端可用 React Context、Redux、Zustand 等管理全局状态
- 结合 SWR/React Query 实现客户端数据自动同步与缓存
4.6.5 SSR 错误处理
- 服务端异常需捕获并友好展示,避免 500 页面直出
- 可自定义
_error.tsx
页面统一处理错误
tsx
// pages/_error.tsx
function Error({ statusCode }) {
return (
<div>
<h1>出错啦!</h1>
<p>错误码:{statusCode}</p>
<a href="/">返回首页</a>
</div>
);
}
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default Error;
最后感谢阅读!欢迎关注我,微信公众号:
《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!!