用Gatsby构建高性能静态网站的实战经验与优化技巧

❤德丽 框架 阅读 2,156
赞 31 收藏
二维码
手机扫码查看
反馈

谁更灵活?谁更省事?Gatsby、Next.js 和纯 Vite 静态站的三选一

上周又接了个客户小项目:一个带博客、产品页、SEO 要求高、但几乎不登录、没用户态、也不需要实时更新的官网。我本来想直接甩个 Gatsby 上去,结果翻了翻自己三年前写的 Gatsby v4 项目,再瞅了眼 node_modules 里那 1800 个依赖,手抖了一下——算了,先对比清楚再动手。

用Gatsby构建高性能静态网站的实战经验与优化技巧

这次就拉上三个常客:Gatsby(v5)、Next.js(App Router + static export)、Vite + React(纯静态生成)。不是教科书式对比,是我这三年在真实项目里踩过坑、删过 config、重写过构建脚本后的真实手感。结论先放前面:我现在新起静态站,90% 选 Vite + React + unplugin-react-router;Gatsby 我只在老项目维护或客户明确要求“必须用 Gatsby”时才碰;Next.js 的 static export 我试过两次,一次成功,一次被 ISR 缓存逻辑绕晕,最后手动关掉所有动态能力才跑通。

代码写起来谁最直觉?

Gatsby 的数据层确实有它的魔力。比如我要从 markdown 拉一篇博客:

// gatsby-node.js
exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  const result = await graphql(
    query {
      allMarkdownRemark {
        nodes {
          frontmatter { slug }
        }
      }
    }
  )
  result.data.allMarkdownRemark.nodes.forEach(node => {
    createPage({
      path: node.frontmatter.slug,
      component: require.resolve('./src/templates/blog-post.js'),
      context: { slug: node.frontmatter.slug }
    })
  })
}

然后在模板里用 GraphQL 查询:

// src/templates/blog-post.js
export const query = graphql
  query($slug: String!) {
    markdownRemark(frontmatter: { slug: { eq: $slug } }) {
      html
      frontmatter { title, date }
    }
  }

说实话,第一次用的时候我觉得这比写 fetch 爽多了——不用管请求时机、loading、错误处理,编译期全搞定。但问题也在这儿:你得记住 GraphQL schema 是怎么被 Gatsby 自动生成的。比如 frontmatter 里有个 tags: [String],它生成的是 tags: [String!] 还是 tags: [String]?改错一个 !,本地 dev 就报错,但控制台提示像天书:“Cannot query field ‘tags’ on type ‘MarkdownRemarkFrontmatter’”。我踩过三次,每次都是删掉 .cache 重来,折腾半小时。

Next.js 的 App Router 做静态导出,就得主动“假装自己是静态的”:

// app/blog/[slug]/page.tsx
export const dynamic = 'force-static' // 必须加!不然默认是 dynamic
export const revalidate = false // 必须关掉 ISR

export default async function BlogPost({ params }) {
  const res = await fetch(https://jztheme.com/api/posts/${params.slug}, {
    cache: 'force-cache', // 这个 cache 是 build-time 的,不是 runtime
  })
  const post = await res.json()
  return <article dangerouslySetInnerHTML={{ __html: post.html }} />
}

这里注意:如果你漏了 dynamic = 'force-static',build 会默默走 SSR 流程,但 export 出来全是 404。我第二次试的时候就是忘了这行,本地 next dev 正常,next build && next export 后打开 index.html 却跳转失败——因为路由没预生成。查文档花了 40 分钟,最后在 GitHub issues 里翻到一句“check dynamic config”,才恍然大悟。

Vite + React 的方案最原始,但也最透明:

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'

export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        blog: resolve(__dirname, 'blog.html'), // 手动声明要生成的 HTML 入口
      }
    }
  }
})

然后我在 blog.html 里写个简单的初始化逻辑:

<!-- blog.html -->
<div id="root"></div>
<script type="module">
  import { createRoot } from 'react-dom/client'
  import BlogList from './src/pages/BlogList.jsx'
  createRoot(document.getElementById('root')).render(<BlogList />)
</script>

数据?我自己 fetch,自己缓存,自己做 loading。没有魔法,但也没有黑盒。哪天 API 改了字段,我 grep 一下 fetch 就知道全在哪改。比翻 Gatsby 的 gatsby-node.js + gatsby-config.js + GraphQL query 三处地方清爽多了。

构建速度和热更新,谁让我少等一会儿?

Gatsby 开发服务器启动时间:平均 12~18 秒(含 .cache 清空后首次)。修改一个 CSS?热更新基本秒出。改一个 GraphQL 查询?大概率要等 3~5 秒重新编译整个数据层。我习惯改完样式切回浏览器,结果发现页面白屏几秒——原来是 GraphQL 报错了,但控制台还没刷出来。

Next.js 的 dev server 启动快(<5 秒),但热更新有时抽风:改了个组件 props,页面不动,得手动刷新。Vite?改 JSX,HMR 基本是 sub-second,连状态都不丢(用了 @preact/signals 做状态管理后更稳)。这点上,Vite 让我每天少点几十次 F5。

SEO 和生成质量,真有差距吗?

三者都能产出干净的 HTML,meta 标签、schema.org 结构化数据、预加载关键资源都没问题。Gatsby 插件生态最全(gatsby-plugin-google-gtaggatsby-plugin-sitemap 都开箱即用),但 Next.js 用 generateStaticParams + metadata API 也能做到一模一样。Vite 需要自己写 sitemap.xml 生成脚本,我抄了个 50 行的 scripts/generate-sitemap.ts,跑在 build 后 hook 里,够用。

唯一让我皱眉的是 Gatsby 的 gatsby-plugin-image。它生成的 <picture> 标签确实智能,但有次客户上传了一张 12MB 的 TIFF,Gatsby 在 build 时卡死在 Sharp 处理环节,日志就一行“Killed”,啥也不说。我 debug 了俩小时才发现是内存溢出,最后只能在 gatsby-node.js 里加 sharp.cache(false)sharp.concurrency(1) 强制降并发——这种底层细节,不该让业务开发者操心。

我的选型逻辑

  • 新项目、纯静态、内容驱动、要快上线 → Vite + React + 自建 router。省心、可控、构建快、debug 直观。我甚至把 markdown 解析也换成 remark + rehype 在 build 时跑,完全脱离运行时解析。
  • 已有 Gatsby 项目、客户愿意续费维护 → 继续用,但会逐步把 GraphQL 替换为 createRemoteFileNode + JSON 数据源,绕开 schema 生成的不确定性。
  • Next.js?除非项目后续明确要加 SSR 功能(比如用户仪表盘),否则我不主动选它做静态站。static export 的配置太容易踩坑,文档分散,社区讨论也常混淆 pagesapp 路由的行为差异。

以上是我的对比总结,有不同看法欢迎评论区交流。另外,如果你也在用 Vite 做静态站,欢迎看我下篇:《如何用 100 行代码在 Vite 里实现 Gatsby-style 页面预生成》。

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

暂无评论