FCP性能优化实战:从加载速度到用户体验的全面提升

UX浩圆 前端 阅读 1,275
赞 14 收藏
二维码
手机扫码查看
反馈

FCP 优化,我为啥越来越不爱用 SSR 了?

最近又在折腾一个新项目,首页加载速度被老板盯上了,说用户流失率高。我一查 Lighthouse,FCP(First Contentful Paint)卡在 3.2s,确实有点拉胯。于是翻出几个常用方案重新对比:SSR、静态生成(SSG)、客户端渲染(CSR)+ 骨架屏,还有混合方案。说实话,以前我挺迷信 SSR 的,觉得“服务端渲染肯定快”,但这次实测下来,发现事情没那么简单。

FCP性能优化实战:从加载速度到用户体验的全面提升

谁更灵活?谁更省事?

先说结论:现在我更倾向用 SSG + 客户端动态水合(hydration)的组合拳,尤其是内容变化不频繁的页面。SSR 虽然理论上能更快出内容,但实际部署和维护成本太高,而且一旦后端慢一点,整个 FCP 就崩了。

举个例子,我用 Next.js 写了个简单页面,分别用 SSR 和 SSG 构建:

// SSR 方案(每次请求都走服务端)
export async function getServerSideProps() {
  const res = await fetch('https://jztheme.com/api/data');
  const data = await res.json();
  return { props: { data } };
}

// SSG 方案(构建时生成静态 HTML)
export async function getStaticProps() {
  const res = await fetch('https://jztheme.com/api/data');
  const data = await res.json();
  return { props: { data } };
}

看起来就差一个函数名,但实际效果天差地别。SSR 每次访问都要等后端 API 返回,如果那个 API 响应慢(比如数据库查得慢),FCP 直接拖到 4s+。而 SSG 在构建时就把数据塞进 HTML,用户打开就是完整内容,FCP 能压到 1.1s 以内——前提是你的内容可以预生成。

这里注意,我踩过好几次坑:SSR 页面里如果用了 useEffect 去二次加载数据,那首屏内容其实是空的,FCP 反而比纯 CSR 还差,因为浏览器要等服务端返回一个“半成品”HTML,再等 JS 执行完才显示内容。这还不如直接上骨架屏。

性能对比:差距比我想象的大

我拿三个方案做了实测(本地开发环境 + 模拟 3G 网络):

  • 纯 CSR(Create React App):FCP 2.8s,白屏时间长,但交互快(JS 加载完立刻可操作)
  • Next.js SSR:FCP 2.5s(依赖 API 速度),但 TTI(Time to Interactive)高达 4.1s,因为要等服务端 HTML + 客户端 JS 双重加载
  • Next.js SSG + 骨架屏:FCP 1.0s,TTI 2.3s,体验最稳

最让我意外的是 SSR 的 TTI。本来以为服务端吐出 HTML 就万事大吉,结果发现客户端还要花大量时间做 hydration,把静态 HTML “激活”成可交互的 React 组件。这个过程如果组件复杂,会卡住主线程,用户看到内容却点不动——这比白屏还让人烦躁。

相比之下,SSG 的 HTML 是纯静态的,hydration 压力小很多。再加上我加了个简单的骨架屏(其实就几行 CSS),用户感知不到“等待”,FCP 数据也好看。

/* 骨架屏 CSS,亲测有效 */
.skeleton {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
}
@keyframes loading {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

我的选型逻辑

现在我选方案基本看两点:内容是否实时?团队能否扛住 SSR 运维?

如果内容更新频率低(比如博客、产品介绍页),我毫不犹豫选 SSG。构建一次,全球 CDN 分发,FCP 稳得一批。而且不用管服务器挂没挂,运维成本几乎为零。

如果内容必须实时(比如后台管理系统、股票行情),我才考虑 SSR。但这时候我会做两件事:

  • 给 SSR 接口加缓存,至少 5 秒内重复请求走缓存,避免后端被压垮
  • 首屏只渲染关键内容,非关键区域用 Suspense + lazy loading,减少 hydration 时间

至于纯 CSR?除非是内部工具或者对 SEO 无要求,否则我基本不碰。FCP 太难看,Lighthouse 分数低,老板看了皱眉。

还有一个折中方案:流式 SSR(Streaming SSR)。Next.js 13 的 App Router 支持这个,能把 HTML 分块流式输出,先吐出头部,再慢慢填充内容。理论上能改善 FCP,但我试了两次,发现实际提升有限,而且调试起来特别麻烦——hydration 错位问题频发,折腾了半天发现不如直接上 SSG。

踩坑提醒:这三点一定注意

1. 别迷信 SSR 的 FCP:如果后端慢,SSR 的 FCP 可能比 CSR 还差。一定要监控真实用户数据,别只看本地开发环境。

2. hydration 别偷懒:SSR/SSG 生成的 HTML 和客户端代码必须严格一致,否则 React 会报错并重新渲染,导致 FCP 无效。我之前因为一个 Date.now() 在服务端和客户端不一致,导致整个页面闪白,查了两天才定位到。

3. 图片和字体是隐形杀手:就算你用了 SSG,如果首屏图片没压缩、字体没预加载,FCP 还是会卡住。记得加 loading="lazy"font-display: swap

<!-- 图片懒加载 -->
<img src="hero.jpg" loading="lazy" alt="">

<!-- 字体优化 -->
<style>
  @font-face {
    font-family: 'MyFont';
    src: url('myfont.woff2') format('woff2');
    font-display: swap;
  }
</style>

最后说两句

FCP 优化没有银弹,但 SSG + 骨架屏这套组合拳,是我目前最省心、效果最好的方案。SSR 不是不能用,但得想清楚代价——你愿意为那 0.3s 的理论提升,多花 30% 的运维精力吗?反正我现在不太愿意了。

以上是我个人对 FCP 优化方案的对比总结,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多(比如结合边缘计算做动态 SSG),后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论