1. 从浏览器到服务器:我们需要SSR的三大理由
打开淘宝商品页时,你会先看到完整的商品详情再加载图片,这背后正是服务端渲染(SSR)在起作用。相比传统React客户端渲染(CSR),SSR最大的优势是将首屏内容直接输出到HTML文档中:
- SEO优化:爬虫直接读取服务端生成的HTML内容
- 首屏加速:用户无需等待JavaScript加载完成就能看到内容
- 数据预取:服务端在渲染时就已经完成数据加载
我们来看一个典型的CSR应用加载流程:
// CSR加载流程示意(React + create-react-app)
function App() {
const [data, setData] = useState(null); // 需要等待数据请求
useEffect(() => {
fetch('/api/data').then(res => setData(res.json()));
}, []);
return data ? <Product data={data} /> : <LoadingSpinner />;
}
// 用户必须等待3个步骤:下载HTML -> 下载JS -> 发起数据请求
2. Next.js的SSR实现三板斧
在Next.js项目中,这三个核心API构成SSR技术栈:
getServerSideProps:为每个请求动态生成页面getStaticProps:构建时生成静态页面Dynamic Routes:动态路径的SSR支持
实战示例1:电商产品页的SSR实现
// pages/products/[id].js
export async function getServerSideProps(context) {
// 从URL参数获取产品ID
const { id } = context.params;
// 服务端直接访问数据库
const product = await db.products.findUnique({
where: { id: parseInt(id) }
});
// 返回的props会传递给页面组件
return {
props: { product }
};
}
function ProductPage({ product }) {
// 无需加载状态,数据直接可用
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
// 这个页面会在每次请求时通过服务端获取最新数据并预渲染
3. 混合渲染策略:让动静结合的艺术
Next.js最大的优势是支持混合渲染,通过合理的路由配置实现动静分离:
| 路由类型 | 适用场景 | 实现方式 |
|---|---|---|
| 动态SSR页面 | 用户中心/实时数据 | getServerSideProps |
| 静态生成页面 | 博客/商品分类页 | getStaticProps |
| 增量静态更新 | 促销活动页/高频更新内容 | revalidate 参数 |
实战示例2:带增量更新的新闻列表
// pages/news/index.js
export async function getStaticProps() {
const newsList = await fetchNews();
return {
props: { newsList },
// 每60秒重新生成页面
revalidate: 60
};
}
function NewsPage({ newsList }) {
// 首次访问是构建时的静态内容
// 之后访问每60秒刷新一次
return (
<div>
{newsList.map(news => (
<article key={news.id}>
<h2>{news.title}</h2>
<p>{news.excerpt}</p>
</article>
))}
</div>
);
}
// 通过ISR实现高性能和时效性的平衡
4. 深水区:SSR项目中的性能优化
实施SSR时需要注意的三大性能陷阱:
4.1 数据获取瀑布流问题
// 错误示例:嵌套的数据获取
export async function getServerSideProps() {
const user = await fetchUser(); // 第一个请求
const orders = await fetchOrders(user.id); // 依赖第一个结果
return { props: { user, orders } };
}
// 正确做法:并行获取
Promise.all([fetchUser(), fetchOrders()]);
4.2 合理使用缓存策略
// 自定义缓存头示例
export async function getServerSideProps({ res }) {
res.setHeader(
'Cache-Control',
'public, max-age=60, stale-while-revalidate=300'
);
// ...数据获取逻辑
}
// 对不常变化的数据添加CDN缓存
4.3 服务端/客户端状态同步
// 使用全局状态管理器保持同步
import { useStore } from '../store';
function UserProfile({ serverData }) {
const { setUser } = useStore();
// 将服务端数据注入客户端状态
useEffect(() => {
if(serverData) {
setUser(serverData.user);
}
}, [serverData]);
return <div>{serverData.user.name}</div>;
}
// 确保两端状态一致性
5. 那些年我们踩过的SSR的坑
实际项目中常见的四个问题及解决方案:
5.1 环境差异问题
// 错误示例:在服务端访问window对象
function Component() {
const [width, setWidth] = useState(window.innerWidth);
}
// 正确解决方案:
typeof window !== 'undefined' && window.innerWidth
5.2 内存泄漏防范
// 在getServerSideProps中及时清理资源
export async function getServerSideProps() {
const controller = new AbortController();
try {
const data = await fetch('/api', {
signal: controller.signal
});
return { props: { data } };
} catch (err) {
// 取消未完成的请求
controller.abort();
}
}
6. 选型指南:何时该用SSR?
适合SSR的应用场景:
- 强SEO需求的官网、电商平台
- 需要即时更新的新闻/社交媒体类应用
- 首屏性能要求极高的移动端网站
不适合SSR的场景:
- 内部管理系统(对SEO无需求)
- 实时交互型应用(如在线绘图工具)
- 基础架构不足(缺乏Node.js运维能力)
Comments