用Gatsby搭建高性能静态网站的实战经验分享

技术庆玲 框架 阅读 626
赞 10 收藏
二维码
手机扫码查看
反馈

优化前:卡得不行

项目是用 Gatsby 搭的,一开始图省事,直接上了 gatsby-starter-default,图片随便扔,组件随便 import,开发体验丝滑得不行。结果一上测试环境跑 Lighthouse,好家伙,首屏加载 5.2 秒,FCP(First Contentful Paint)3.8 秒,CLS(Cumulative Layout Shift)高得离谱。用户点个链接跟等电梯似的,中间白屏一下,然后文字突然蹦出来,图片再慢慢加载——这哪是网站,这是行为艺术。

用Gatsby搭建高性能静态网站的实战经验分享

最离谱的是在中端安卓机上,交互延迟特别明显,点了按钮要半秒才响应,我一度怀疑是不是我自己写的事件绑定有问题。但其实问题不在交互逻辑,而是整个页面 load 太重,JS 堆太多,主线程被占满了。

找到瘼颈了!

先甩出 Lighthouse 和 Chrome DevTools Performance 面板,一顿操作。重点看了以下几个指标:

  • 资源加载瀑布图:一堆图片和 JS 文件并行加载,首屏关键资源没优先级
  • 主线程活动:一大堆 JavaScript 在解析和执行,尤其是第三方库
  • DOM 结构:虽然静态生成了 HTML,但 hydration 阶段太慢

还用了 Gatsby 自带的 gatsby-plugin-webpack-bundle-analyzer 插件,跑了个分析报告。打开一看,心凉了半截——一个叫 lodash 的包占了 180KB,还有一个 UI 组件库我把整个 Modal 库都 import 了,实际只用了两个弹窗……

另外,图片全是从 CMS 直接吐出来的原图,宽度 2000px,体积动辄 800KB,Gatsby 虽然能做图片优化,但我 TM 忘了用 gatsby-plugin-image,全都是普通 <img> 标签硬塞进去的。

总结下来就是三个大问题:

  1. JS 包太大,tree-shaking 没做好
  2. 图片没优化,懒加载也没开
  3. 非关键资源阻塞了首屏渲染

动刀:拆包 + 图片重构

先从 JS 开始。第一步,把所有 import _ from 'lodash' 全干掉,换成按需引入:

// 优化前
import _ from 'lodash';
_.debounce(handleSearch, 300);

// 优化后
import debounce from 'lodash/debounce';
debounce(handleSearch, 300);

顺手加上 babel-plugin-lodash,让它自动帮你做这个转换,省得手动改到手软。

接着是组件库。我用的是一个内部封装的 UI 库,之前为了方便,全量引入了:

// 优化前
import { Button, Modal, Toast } from '@company/ui-library';

// 优化后 - 改成动态导入 + 异步加载
const Modal = React.lazy(() => import('@company/ui-library/modal'));
const Toast = React.lazy(() => import('@company/ui-library/toast'));

配合 Suspense 使用,非首屏用的组件一律懒加载:

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Modal />
    </Suspense>
  );
}

这一波操作下来,主 bundle 从 420KB 干到了 190KB,gzip 后 68KB,直接砍掉一半多。

图片必须上 gatsby-plugin-image

图片这块我真是踩坑踩到自闭。一开始以为 Gatsby 自动生成 responsive images 就完事了,结果发现如果你不用官方推荐的组件,它根本不会触发优化流程。

原来的写法:

<img src="https://jztheme.com/images/hero.jpg" alt="Hero" />

改成:

import { GatsbyImage } from 'gatsby-plugin-image';
import { getImage } from 'gatsby-plugin-image';

function BlogPost({ data }) {
  const image = getImage(data.file.childImageSharp);
  return <GatsbyImage image={image} alt="Hero" />;
}

并且在 graphql 查询里明确指定大小:

query {
  file(relativePath: { eq: "hero.jpg" }) {
    childImageSharp {
      gatsbyImageData(width: 800, placeholder: BLURRED, formats: [WEBP])
    }
  }
}

这样生成的图片会自动有:

  • WebP 格式支持(现代浏览器)
  • 模糊占位符(blur-up)防止布局偏移
  • 懒加载(默认开启)
  • 响应式 srcset

这一改,首屏图片总加载量从 1.2MB 降到 320KB,而且因为有占位图,CLS 直接从 0.35 降到 0.02,几乎感觉不到抖动。

第三方脚本别乱放

项目里嵌了个统计脚本,本来是直接扔 index.html 里的,结果发现它同步阻塞了页面解析。后来改用异步加载:

// utils/analytics.js
export function loadAnalytics() {
  const script = document.createElement('script');
  script.src = 'https://jztheme.com/analytics.js';
  script.async = true;
  script.defer = true;
  document.head.appendChild(script);
}

// 在页面 mount 后调用
useEffect(() => {
  loadAnalytics();
}, []);

或者更优雅地用 react-helmet-async 控制注入时机:

import { Helmet } from 'react-helmet-async';

<Helmet>
  <script async defer src="https://jztheme.com/analytics.js" />
</Helmet>

避免第三方脚本拖慢首屏。

其他小修小补

还有一些零碎但有用的调整:

  • 加了 gatsby-plugin-preload-fonts 预加载关键字体,避免 FOIT(Flash of Invisible Text)
  • Link 替换所有 <a href> 跳转,启用预加载(Gatsby 默认 prefetches 页面)
  • 移除了几个开发阶段遗留的 debug 工具,比如 why-did-you-render,这玩意在生产环境也有开销
  • 检查了所有 useEffect,确保没有频繁触发 re-render

优化后:流畅多了

改完部署上线,重新跑了一轮测试,数据直接起飞:

  • 首屏加载时间:从 5.2s → 800ms
  • FCP:3.8s → 1.1s
  • LCP:4.1s → 1.3s
  • CLS:0.35 → 0.02
  • JS 总下载量:1.1MB → 380KB(gzip)

现在在 3G 网络模拟下也能保持 1.5s 内出内容,用户体验肉眼可见变好了。最明显的感受是:点链接不再“卡一下”,返回也几乎是秒回,因为页面已经被 prefetch 了。

不过也不是 100% 完美。有个小问题是,在极低端设备上,hydration 还是有点小卡顿,特别是长列表页。我试过用 React.memowindowing,但考虑到业务场景用户量少,就暂时放着了。毕竟性能优化是个权衡游戏,不能为了 1% 的场景搞得太复杂。

性能数据对比

下面是优化前后 Lighthouse 分数的变化(满分 100):

  • Performance:32 → 93
  • Accessibility:78 → 90
  • Best Practices:65 → 98
  • SEO:88 → 96

最关键的 Performance 分翻了快三倍。虽然不是每个项目都能冲到 90+,但至少说明方向没错。

以上是我的优化经验,有更好的方案欢迎交流

这次折腾大概花了三天,中间踩了不少坑,比如一开始想自己写 Image 组件做懒加载,结果发现 gatsby-plugin-image 早就帮你搞定了,纯属重复造轮子。还有一次误删了 gatsby-browser.js 里的配置,导致 prefetch 失效,调试了半天才发现。

核心经验就几点:

  • 别迷信 SSR/SSG,生成得快不代表加载快
  • JS 包大小是第一杀手,尤其第三方库
  • 图片必须用 gatsby-plugin-image,别偷懒
  • 工具要善用,Lighthouse + Bundle Analyzer 是标配

这套方案不是最优解,但足够简单、稳定、见效快。如果你也在用 Gatsby 做内容型站点,这些优化基本能 cover 90% 的性能问题。

以上是我踩坑后的总结,希望对你有帮助。如果有更优的实现方式,欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论
IT人淑瑶
作者对知识点的归纳总结很到位,帮我快速抓住了核心,学习效率提升明显。
点赞
2026-03-25 11:26