首页技术专题博客目录我的收藏关于与联系

SSR 的性能陷阱:Next.js 项目优化记

用 Next.js 做了个 SSR 项目,上线后发现服务端渲染慢得不行,TTFB 经常超过 2 秒。排查了一个月,发现是数据获取和缓存的问题。这篇文章记录一下优化过程。

问题出现

项目上线后,用户反馈页面加载很慢。我看了下监控,发现服务端渲染时间(SSR Time)平均 1.8 秒,最慢的页面要 3 秒多。这肯定有问题。

更奇怪的是,开发环境很快,生产环境就慢。一开始以为是服务器配置问题,但换了更好的服务器,还是慢。

排查过程

第一步:看日志

先看服务端日志,发现每个请求都要等很久。用 console.time 打点,发现 getServerSideProps 执行时间特别长。

第二步:分析数据获取

我们的 getServerSideProps 里做了好几件事:

  • 调用用户 API 获取用户信息
  • 调用内容 API 获取页面内容
  • 调用推荐 API 获取推荐内容
  • 调用统计 API 获取统计数据

这些 API 调用都是串行的,每个要等 200-500ms,加起来就 1-2 秒了。

第三步:发现重复请求

更严重的是,我们发现同一个 API 在同一个请求里被调用了多次。比如用户信息,在 header 组件里调一次,在 sidebar 组件里又调一次。

虽然 Next.js 有请求去重(request deduplication),但只对 fetch 有效。我们用的是 axios,就没这个优化。

优化方案

方案一:并行请求

把串行的 API 调用改成并行的:

// 之前 const user = await getUser(); const content = await getContent(); const recommendations = await getRecommendations(); // 之后 const [user, content, recommendations] = await Promise.all([ getUser(), getContent(), getRecommendations() ]);

这一改,数据获取时间从 1.5 秒降到了 600ms,效果很明显。

方案二:请求去重

统一用 Next.js 的 fetch,利用它的请求去重功能。但有些第三方库只能用 axios,我们就自己实现了一个简单的去重机制。

在请求层面加了个缓存,同一个请求在 100ms 内的重复调用,直接返回缓存结果。

方案三:ISR + 增量更新

对于不经常变的内容,用 ISR(Incremental Static Regeneration)。比如文章详情页,设置 revalidate: 3600,每小时更新一次。

这样大部分请求直接返回静态页面,只有第一次或者更新时才需要服务端渲染。

方案四:数据库查询优化

有些慢是因为数据库查询慢。我们加了索引,优化了查询语句,还用了 Redis 做缓存。

对于热点数据,比如首页推荐,直接缓存 5 分钟,减少数据库压力。

遇到的坑

坑一:ISR 的缓存失效

ISR 用起来很爽,但缓存失效是个问题。我们有个需求是文章发布后立即更新,但 ISR 的缓存还在,用户看不到新内容。

后来用了 On-Demand Revalidation,文章发布时调用 API 手动清除缓存,才解决了这个问题。

坑二:并行请求的依赖

有些请求之间有依赖关系,不能完全并行。比如获取推荐内容,需要先知道用户信息。

我们做了个依赖图,先并行获取独立的数据,再串行获取有依赖的数据。

坑三:内存泄漏

优化过程中,发现服务端内存一直在涨。排查后发现是请求去重的缓存没有清理,时间长了就内存泄漏了。

加了 LRU 缓存,限制缓存大小,才解决了这个问题。

优化效果

优化完成后:

  • SSR 时间从 1.8 秒降到 400ms
  • TTFB 从 2 秒降到 500ms
  • 服务器 CPU 使用率从 80% 降到 40%
  • 数据库查询次数减少 60%

经验总结

SSR 的性能问题,大部分都是数据获取的问题:

  • API 调用要并行,不要串行
  • 重复请求要去重
  • 能静态化的就静态化,用 ISR
  • 数据库查询要优化,加缓存

另外,开发环境和生产环境的性能差异,往往是因为数据量不同。开发环境数据少,看不出问题。生产环境数据多,问题就暴露了。

如果重新开始

如果重新做一个 SSR 项目,我会:

  • 一开始就考虑数据获取的性能
  • 统一用 Next.js 的 fetch,利用请求去重
  • 能静态化的页面就用静态生成
  • 做好监控,及时发现问题

总结

SSR 不是银弹,性能问题要提前考虑。数据获取、缓存策略、数据库优化,这些都是关键。

如果你们项目也有 SSR 性能问题,建议先看数据获取,再看缓存,最后看数据库。大部分问题都能在这几个地方找到。

评论区