跳转到主要内容

本地预览

用 hugo server 在本机预览改动,用 hugo –panicOnWarning 构建可部署的 public/,不需要 Node 与 CDN。

两条命令覆盖日常工作:hugo server 在本机预览改动,hugo 产出可以部署到任何静态托管的 public/。前提是本机安装了 Hugo Extended(不低于 0.160.1);用 Hugo Module 引入主题时还需要 Go。构建不依赖 Node.js、npm 与 PostCSS,它们只服务于本仓库自身的回归检查。

预览服务器

在站点根目录(hugo.yml 所在的目录)执行:

终端
hugo server

打开 http://localhost:1313/。保存文件后 Hugo 重新构建并刷新浏览器,切换 Git 分支同样触发重建。首次启动较慢:用 Hugo Module 引入主题时,Hugo 要先通过 Go 把模块下载到缓存,之后的启动都走缓存。

常用开关

-D / --buildDrafts , default
draft: true 的页面也构建出来
-F / --buildFuture , default
date / publishDate 在未来的页面也构建出来
-E / --buildExpired , default
expiryDate 已过的页面也构建出来
--disableFastRender , default
每次改动都整站重渲染,不用增量
-M / --renderToMemory , default关(写磁盘)
只在内存里渲染,不落 public/
-N / --navigateToChanged , default
保存哪个页面,浏览器就跳到哪个页面
--bind , default127.0.0.1
监听地址;要让局域网或容器外访问就设 0.0.0.0
-p / --port , default1313
监听端口
--minify , default
预览也压缩输出,用来复现生产环境下的渲染
--printPathWarnings , default
有两个页面写到同一个目标路径时告警

本站开发时用的组合是:

终端
hugo server -DFE \
  --disableFastRender --renderToMemory --minify \
  --printPathWarnings --logLevel info

-DFE-D -F -E 的合写,草稿、未来与过期页面一并构建,写作时新建的页面才可见。

改动没有生效

Hugo 默认开启快速渲染(fast render),只重建它判定受影响的部分。修改布局、配置、data/ 或被 include 引用的文件时,增量判定可能不准,页面看起来没有变化。三步排查:

  1. --disableFastRender 重启,看是否恢复。
  2. 硬刷新浏览器(Cmd/Ctrl + Shift + R),排除浏览器缓存。
  3. 仍未恢复则清缓存后重启。

从其它设备访问

hugo server 默认只监听 127.0.0.1,其它设备访问不到。要在手机或另一台机器上预览:

终端
hugo server --bind 0.0.0.0 --port 1313 --baseURL http://192.168.1.10:1313/

--baseURL 必须写成对方可访问的地址,否则页面能打开,但 CSS 与搜索索引这类走绝对路径的资源会指向 localhost

生产构建

部署产物用 hugo 构建,不用 hugo server

终端
hugo --gc --minify --printPathWarnings --panicOnWarning

产物写入 public/,该目录可以脱离源码树独立部署。四个开关各管一件事:

--gc
构建后清掉 resources/_gen 里不再被引用的缓存资源
--minify
压缩 HTML、CSS、JS 与 XML 输出
--printPathWarnings
两个页面撞到同一个输出路径时告警,多语言站点最常见的静默错误
--panicOnWarning
遇到第一条 WARNING 就让构建失败

--panicOnWarning 需要单独说明。OINK 的多数降级路径是告警而不是报错:giscus 必填键缺失、params.comments.type 取了不支持的值、Hugo 弃用的配置键,都只打一条 WARNING 然后跳过。CI 日志通常无人逐行阅读,这些问题会带到线上。把这个开关写进构建命令,等于要求零告警才算构建通过。

本站 CI 的构建步骤(.github/workflows/pages.yml)是 hugo --cleanDestinationDir --gc --minify --environment production --printPathWarnings --panicOnWarning,任何一条告警都会让部署停在构建阶段。

baseURL 与构建环境

baseURL 写在 hugo.yml 里,也可以在命令行覆盖:

hugo.yml
baseURL: https://oink.pgsty.com
终端
hugo --minify --baseURL "https://example.com/docs/"

部署到子路径时 --baseURL 必须带上那段路径,细节见发布上线

构建环境用 -e / --environment 选择,hugo 默认 productionhugo server 默认 development。这个选择在 OINK 里有三处可见后果:

  • production 下才输出 <meta name="robots" content="index, follow">,其它环境输出 noindex, nofollow
  • productionrobots.txtAllow: /,其它环境是 Disallow: /
  • production 下才渲染 Hugo 的 Google Analytics 模板,静态资源也才做指纹与 SRI。

预览部署(PR preview、staging)用非 production 环境构建,产物自带不被搜索引擎收录、不上报分析的行为:

终端
hugo --minify --environment staging --baseURL "$PREVIEW_URL"

容器内预览

容器不是必需的。两种情况适合用容器:团队需要固定工具链版本,或不希望在每台开发机上安装 Hugo。

Dockerfile
FROM debian:bookworm-slim

ARG HUGO_VERSION=0.164.0
ARG GO_VERSION=1.26.6
ARG TARGETARCH

RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates curl git \
    && curl -L -o /tmp/hugo.deb \
      "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-${TARGETARCH}.deb" \
    && apt-get install -y /tmp/hugo.deb \
    && curl -L -o /tmp/go.tgz \
      "https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz" \
    && tar -C /usr/local -xzf /tmp/go.tgz \
    && rm -rf /var/lib/apt/lists/* /tmp/hugo.deb /tmp/go.tgz

ENV PATH="/usr/local/go/bin:${PATH}"
WORKDIR /src
EXPOSE 1313
ENTRYPOINT ["hugo"]
CMD ["server", "--bind", "0.0.0.0", "--disableFastRender"]
终端
docker build -t oink-hugo .

# 预览:把站点源码挂进去,顺带挂上 Go 模块缓存
docker run --rm -it -p 1313:1313 \
  -v "$PWD:/src" \
  -v "$HOME/go/pkg/mod:/root/go/pkg/mod" \
  oink-hugo

# 生产构建:覆盖默认的 server 命令
docker run --rm --user "$(id -u):$(id -g)" \
  -v "$PWD:/src" \
  oink-hugo --gc --minify

镜像里装 Go 的原因:用 Hugo Module 引入主题时,Hugo 需要 Go 解析并下载模块。用 submodule、离线归档或直接克隆的站点可以去掉 Go,镜像会小很多。

不要让 root 写 public/

容器里的进程默认是 root,生成的 public/ 属于 root,宿主机上删不掉。共享环境里用 --user "$(id -u):$(id -g)" 映射用户 ID(上面的生产构建命令已经带了)。

镜像不需要 Node.js、npm 与 PostCSS,也不应出现拉取远程浏览器资源的步骤。网络隔离环境需要预先镜像基础镜像与这两个软件包。

清缓存

Hugo 的中间产物分三处,从轻到重依次清:

public/ , 内容上一次的构建产物
删了页面但线上还在;或用 hugo --cleanDestinationDir 让构建自己清
resources/_gen/ , 内容处理过的图片与编译出的 CSS
换了图片处理参数、换了字体或主色,页面还是旧样子
hugo mod clean , 内容Hugo Module 缓存
换了主题版本但解析出来还是旧的;加 --all 清整个模块缓存
终端
rm -rf public resources/_gen
hugo mod clean          # 只清当前项目用到的模块
hugo mod clean --all    # 清整个模块缓存,下次构建重新下载

public/resources/ 都应该写进 .gitignore,不要提交生成产物。

与主题一起改

同时修改主题与站点时才需要这一节。用 HUGO_MODULE_REPLACEMENTS 把模块临时指向本地 checkout,go.mod 保持不变:

终端
HUGO_MODULE_REPLACEMENTS='github.com/pgsty/oink -> /absolute/path/to/oink' hugo server

本站的 Makefile 封装了这几条命令,要求主题 checkout 在同级目录 ../oink

Makefile 目标
make dev     # 替换为 ../oink 的开发服务器
make check   # 替换为 ../oink 跑完整回归套件(npm test)
make build   # 用 go.mod 里的版本构建
make serve   # 按生产配置起预览服务器
替换只作用于本机

无论用环境变量还是 Go workspace(go work init + HUGO_MODULE_WORKSPACE=go.work),CI 与生产构建都只看 go.modgo.work 记录的是开发机的路径,不能提交。判定一个发布标签是否可用时,去掉替换、用 go.mod 里的版本单独构建一次。

断网构建验证

网络隔离环境的验收要同时覆盖构建阶段与浏览器阶段。六步:

  1. 从一份已校验的主题归档与空的模块缓存开始(hugo mod clean --all)。
  2. 阻断出站 HTTP、HTTPS 与 Go module proxy。
  3. 运行生产构建 hugo --gc --minify --printPathWarnings --panicOnWarning
  4. 浏览产物里两种语言的页面:文档页、博客页、首页、404。
  5. 操作搜索、深浅色切换、图表与内容组件。
  6. 检查子资源来源,确认没有意外的远程主机。

最后一步用主题仓库里的脚本,它不依赖站点的测试框架:

终端
python3 bin/check-output-security.py \
  --public public --base-url https://docs.internal.example.com/

脚本扫描四种输出里的每个 href / src / srcset / poster 与表单 action,要求它们是站内相对路径或 http / https / mailto / tel,并拒绝行内 on* 事件处理器与 javascript: URL。指向别的主机的 <iframe> <script> <link> <img> <video> <audio> <embed> <object> <source> 一律报错,站点确实要嵌入第三方内容时加 --third-party 放行,多域名语言配置用 --allow-host 追加首方主机。

一次通过只证明当次提交与当次环境。每个主题候选版本、每次随附依赖更新之后都要重跑一遍。

验证

一次干净的生产构建应该是这样:

终端
rm -rf public resources/_gen
hugo --gc --minify --printPathWarnings --panicOnWarning

看到 Total in … 且没有 ERROR / WARNING 才算通过。然后确认:

  • 日志里没有 npm、PostCSS、Autoprefixer 或下载浏览器资源的步骤。出现了说明配置里混进了上游 Docsy 的流程。
  • public/ 下有 sitemap.xmlrobots.txtrobots.txtAllow: /
  • 开了本地搜索的站点,public/ 根下有 offline-search-index.<语言>.json
  • hugo server 打开代表性页面:一个文档页、一个博客页、首页、404,两种语言、两种配色都看一遍。

构建失败或结果不对,去排错与检查