先说个数:我把跑了两年的文档站换了个框架,首页从 1.8 秒降到 0.4 秒。
不是换服务器,不是加 CDN,就是换了个生成器。服务器还是那台 2 核 4G 的机器。
算完这个数我盯着 curl 的输出看了半天,怀疑是本地缓存骗我,把浏览器关了重跑三次,还是 0.4 秒。
"
结论先放这:如果你只有一个内容站点,页面不多、改完就发、平时不登录,别上 Next.js。Hugo 能干的活,没必要用一个带 Node 运行时和服务端的框架去干。
背景交代一句。我做宽带和号卡生意,客户需要在网站上查套餐、看覆盖小区、读办理说明。这个东西最初是我自己用 Next.js 搭的,功能不算多,但内容一天天堆,现在有 120 多篇文档、40 多个套餐页。
问题出在上个月。有个客户打电话来,说“你们网站打不开”。我自己打开看,等了将近两秒才出来首屏。
我知道两秒对很多人不算什么,但这是静态内容——一个只有文字和几张图的说明页,凭什么要两秒?
— 图1:两个框架在同一批页面上的首屏耗时
改之前我列了三个方向,每条都有代价:
继续用 Next.js,把动态渲染改成纯静态导出(`output: ’export’`)
换 Hugo,Go 写的静态生成器,一个二进制文件
下面是我当时记的表,数字是这台机器上跑出来的:
先说 Next.js,我没换它,不是因为不好,是因为它的重心不在这里。它是给“需要服务端、需要登录、需要接口”的站点准备的。我这个站点这些全都不需要。
— 图2:三个方案的构建耗时对比
Hugo 我试了三天,中间卡了两次。
第一次卡在模板语法。它用的不是 JSX,是 {{ }} 那一套,循环、条件判断、变量作用域都得重新记。我第一天写了半小时,页面一直报 template: partials/head.html:12: unexpected “}}” in operand,改了四遍才发现是少了一个 end。
$ hugo --minify
Error: error building site: Tpl: template: partials/head.html:12: unexpected “}}” in operand
$ grep -n “{{” layouts/partials/head.html | sed -n ’9,13p’
10: <title>{{ .Title }}</title>
11: {{ with .Site.Params.description }}
12: <meta name=“description” content=“{{ . }}>
13: {{ end }}
第 12 行引号没闭合,右边少了一个 “。这种错误跟新人用 Jinja 时一模一样,跟框架好坏没关系,纯手生。
第二次卡的地方更实际:实时预览。我这人改文案的习惯是「改一句,看一句」,所以必须有个边改边刷新的本地服务。
$ hugo server --port 1417
Web Server is available at http://localhost:1417/
启动之后我改了 content/posts/hello.md,保存,刷新浏览器——页面还是旧的。
我以为热更新坏了,重启服务、换端口、清缓存,折腾了二十分钟。最后翻开日志才看见:
$ tail -3 hugo.log
WARN found no layout file for “html” for kind “section”
新加的文件里 draft = true 没改。Hugo 默认不给草稿渲染,所以它不是“没刷新”,是“这个页面压根没生成”。
这一条我后来写进了自己的笔记:改完看不见,先看 front matter 的 draft,再看布局文件有没有缺失。日志里的 WARN 不是噪声,是答案。
把 draft 改成 false 之后,再保存一次,页面立刻变了,构建日志打了一行 Total in 2 ms。
两毫秒。这个数字就是我当时决定换的理由——不是因为它快得离谱,是因为它快到我不用等。
— 图3:Hugo 的目录结构和三条命令
换过来之后,部署变成了复制一个目录。
$ hugo --minify --gc
Start building sites …
| EN
-------------------+-----
Pages | 163
Static files | 41
Total in 512 ms
$ du -sh public
9.1M public
$ rsync -az public/ root@server:/var/www/site/
没有 npm ci,没有 node_modules,没有“本地能跑服务器上跑不了”。服务器上只需要一个 nginx,它连 Hugo 都不用装——构建在我这台开发机上做完,传上去的就是一堆 html。
这个变化对运维的意义比速度更大。我以前的部署脚本里有一半的失败都出在依赖上:某台机器 Node 版本不对、某个包在 arm 上编译不过、锁文件冲突。现在这些全没了。
Hugo 不是新东西,9 万多星、2013 年就有了,它不酷,社区里也没什么热闹话题。
但我觉得它被低估的地方恰恰在这里:一个不需要你陪着的框架,比一个很酷但要你伺候的框架值钱。你一个月不开这个站,回来它照样一条命令构建完。
反过来说,什么情况下我不建议你用 Hugo:
1站点需要登录、需要接口、需要后台 → 别考虑它,它做不了,硬上就是拿锤子拧螺丝
2团队里没人愿意学 Go 模板语法 → Astro 或者 Next.js 静态导出更顺,代价是构建慢十倍左右
3页面要大量交互(图表筛选、实时搜索) → Hugo 得配 JS,配完之后你等于又维护了一套前端
我的判断标准很简单:一个月改不到三次、页面以文字为主、没有登录,这三个条件满足两个以上,我会直接推荐它。
最后留个问题:那个 0.4 秒,我用的是 curl -w 量的服务器响应,不是浏览器里的首屏渲染时间——你们的站点,是看这两个数里的哪一个?
06PART 06
补一段:迁移过程里真正费时间的不是框架
框架本身我一天就迁完了,因为 Markdown 文件几乎是原样搬过去的。真正费时间的是那些“附带的活”。
第一个是图片路径。原来的站点图片放在 /public/uploads/ 下面,文章里写的是绝对路径 /uploads/xxx.png。Hugo 的 static 目录是直接映射到根路径的,所以这部分基本没动,一行 sed 就换完了:
$ grep -rl “/uploads/” content/ | wc -l
27
$ grep -rl “/uploads/” content/ | xargs sed -i ’s#/uploads/#/images/#g’
第二个是旧链接。有 40 多个套餐页的地址里带日期和分类前缀,直接换框架会全部 404。我给 nginx 加了一段跳转,把老的路径模式重写到新位置,改完之后抽查了 30 个链接,全部 200。
第三个比较隐蔽:原来 Next.js 生成的页面里有一层自动加的 og:image 和结构化数据,换过来之后这些标签没了,站长后台里的收录掉了一截。补回来花了两个小时,写了一个 partial,所有页面共用。
这三个活加起来占了我迁移时间的三分之二。所以如果你在评估换不换,别只看“构建从 47 秒到 0.5 秒”,真正的账是:
页面体积省下的
38MB 到 9MB,流量费能感觉到
后续维护
从“每月总得修一次依赖”变成“半年不用管”
最后一行的价值我个人觉得最大,但它没法写进任何一张对比表里。你只有在被依赖问题咬过一次之后,才会知道“没有依赖”这四个字值多少钱。
互动 · TALK
你们的站点是看服务器响应,还是看浏览器首屏?这两个数差得可能比框架还大。
觉得有用,欢迎点赞、在看、转发
THANKS FOR READING