Hugo 文章不显示怎么办:排查草稿、发布时间、过期时间与输出路径
Hugo 返回构建成功,新文章却找不到:可能是草稿,也可能是服务器时区让发布时间落在未来,还可能是页面已生成但地址猜错了。本文面向初级到中级读者,先查看 Hugo 实际识别的元数据,再用全新目录区分“未生成”和“未展示”。
本文衔接发布前站内链接检查。整个排查过程只写临时目录,无需修改主题或服务器配置。
1. 方法:先区分三种情况
- 发布条件未通过:检查
draft、生效的publishDate和expiryDate。 - 没有页面文件:在新输出目录中查找 HTML,再检查构建选项和模板。
- 文件存在但首页没有:核对主题的文章类型筛选、分页和页面集合,而不是反复改发布时间。
hugo list all 包含草稿、未来和过期内容;hugo list published 排除这三类。它们反映内容资格,不是线上 HTTP 可用性的证明。
2. 环境准备
需要已有 Hugo 可执行文件及 Python 3.9+,无需第三方 Python 包。本文实验在 Linux、Hugo 0.164.0 和 Python 3.11.4 下验证。先在项目根目录运行:
hugo version
python3 --version
hugo list all --help
hugo list published --help
下面使用 bash/zsh。把 hugo_config 改成真实发布流程所用的配置文件;例如本网站使用 hugotest.toml。同时保持 --environment 等选项与正式构建一致,避免诊断另一套配置。
3. 列出被排除的文章
在同一个终端执行:
hugo_config=hugo.toml
audit_dir=$(mktemp -d)
trap 'rm -rf -- "$audit_dir"' EXIT
hugo list all --config "$hugo_config" > "$audit_dir/all.csv"
hugo list published --config "$hugo_config" > "$audit_dir/published.csv"
python3 - "$audit_dir" <<'PY_AUDIT'
import csv
from pathlib import Path
import sys
root = Path(sys.argv[1])
with (root / "published.csv").open(encoding="utf-8", newline="") as stream:
published = {row["path"] for row in csv.DictReader(stream)}
with (root / "all.csv").open(encoding="utf-8", newline="") as stream:
for row in csv.DictReader(stream):
if row["kind"] == "page" and row["path"] not in published:
print(row["path"])
for key in ("draft", "date", "publishDate", "expiryDate", "permalink"):
print(f" {key}: {row[key]}")
PY_AUDIT
输出会显示被排除文章的路径及关键字段。例如:
content/posts/new-guide.md
draft: false
date: 2024-01-01T00:00:00Z
publishDate: 2030-01-01T00:00:00Z
expiryDate: 0001-01-01T00:00:00Z
permalink: https://example.org/new-guide/
这是示意输出;你的文件名和域名会不同。0001-01-01... 是未设置过期时间时可能出现的零值,不表示文章在公元 1 年已经过期。脚本用 csv.DictReader 读取列,避免标题中的逗号破坏字段对齐;没有输出只表示没有找到上述差集。
日期解析配置决定生效值:默认发布日先找 publishdate、pubdate、published,最后回退到 date。所以只改 date,不能覆盖另一个仍在未来的 pubdate;项目自定义了 [frontmatter] 时还要检查它。读取列表中的结果,比只看 Markdown 某一行更可靠。
4. 根据原因修正,再构建到新目录
| 现象 | 具体处理 |
|---|---|
draft: true |
文章确实准备发布后改成 draft: false。 |
publishDate 晚于当前时间 |
核对 date -Is 和日期的时区;立即发布的文章应修正实际生效的发布字段。 |
有效 expiryDate 已经过期 |
核实过期策略,继续保留的文章删除误设的过期字段或改为合适时间。 |
| 资格正常,但访问旧地址 404 | 按列表中的 permalink 查输出,核对 front matter 的 url 和语言路径。 |
pubdate 是 publishDate 的别名。同一文章选择一种写法;日期带上 +08:00 或 Z 等偏移量,避免按错误时区理解。
修正自己的目标文章后运行普通构建:
preview_dir=$(mktemp -d)
hugo --config "$hugo_config" --destination "$preview_dir"
find "$preview_dir" -type f -name '*.html' | head -n 20
printf 'Preview directory: %s\n' "$preview_dir"
按实际地址查找文件:根路径站点的 /guide.html 通常对应 guide.html,/guide/ 对应 guide/index.html。多语言或子路径部署要结合配置核对。Hugo URL 文档说明了 url 如何覆盖默认路径。
需要对照隐藏内容的渲染结果时,再建一个独立目录:
diagnostic_dir=$(mktemp -d)
hugo --config "$hugo_config" --buildDrafts --buildFuture --buildExpired \
--destination "$diagnostic_dir"
printf 'Diagnostic directory: %s\n' "$diagnostic_dir"
这些构建参数仅用于本地诊断。只在诊断目录出现的页面说明发布条件需要处理;不要把该目录同步到线上。原有输出目录可能留着上一轮生成的文件,不能用来证明本轮确实渲染了文章。
若普通构建仍缺 HTML,检查日志和模板;同时查看文章、父级 cascade 中的 build.render 与 build.list。render: never 不生成页面,list: never 会使页面退出集合。若 HTML 存在但首页缺文章,还要检查主题是否只列出特定 type 或 section,以及分页位置。
检查完只删除本次创建的两个目录:
rm -rf -- "$preview_dir" "$diagnostic_dir"
5. 验证:可重复的四文章实验
将下面代码保存为 test_hugo_visibility.py。它在临时站点创建四个测试文件,固定 Hugo 的诊断时钟,然后检查列表及五种构建组合;不读取或改动你的站点文章。程序结束时自动清理临时站点。
"""Verify Hugo visibility rules in a temporary site. Python 3.9+."""
import csv
import io
from pathlib import Path
import subprocess
from tempfile import TemporaryDirectory
CLOCK = "2026-01-01T00:00:00Z"
def run_hugo(root, *args):
result = subprocess.run(
["hugo", "--source", str(root), "--clock", CLOCK, *args],
check=True, capture_output=True, text=True, encoding="utf-8", timeout=60,
)
return result.stdout
with TemporaryDirectory(prefix="hugo-visibility-") as work:
root = Path(work)
(root / "content").mkdir()
layouts = root / "layouts" / "_default"
layouts.mkdir(parents=True)
(root / "hugo.toml").write_text(
'baseURL = "https://example.org/"\n'
'disableKinds = ["home", "taxonomy", "term", "RSS", "sitemap"]\n',
encoding="utf-8",
)
(layouts / "single.html").write_text(
"<!doctype html><title>{{ .Title }}</title>{{ .Content }}",
encoding="utf-8",
)
cases = {
"visible": "",
"draft": "draft: true\n",
"future": "publishDate: 2030-01-01T00:00:00Z\n",
"expired": "expiryDate: 2020-01-01T00:00:00Z\n",
}
for name, extra in cases.items():
(root / "content" / f"{name}.md").write_text(
f"---\ntitle: {name}\ndate: 2024-01-01T00:00:00Z\n"
f"url: /{name}.html\n{extra}---\n\nFixture: {name}.\n",
encoding="utf-8",
)
all_rows = list(csv.DictReader(io.StringIO(run_hugo(root, "list", "all"))))
published = list(csv.DictReader(io.StringIO(run_hugo(root, "list", "published"))))
assert {row["title"] for row in all_rows} == set(cases)
assert {row["title"] for row in published} == {"visible"}
builds = [
([], {"visible"}),
(["--buildDrafts"], {"visible", "draft"}),
(["--buildFuture"], {"visible", "future"}),
(["--buildExpired"], {"visible", "expired"}),
(["--buildDrafts", "--buildFuture", "--buildExpired"], set(cases)),
]
for index, (flags, expected) in enumerate(builds):
destination = root / f"output-{index}"
run_hugo(root, "--destination", str(destination), *flags)
actual = {path.stem for path in destination.glob("*.html")}
assert actual == expected, (flags, actual, expected)
for name in expected:
assert f"Fixture: {name}." in (destination / f"{name}.html").read_text()
print("PASS: inventories and 5 isolated builds")
运行:
python3 test_hugo_visibility.py
预期输出 PASS: inventories and 5 isolated builds。固定时间仅用于实验,正式构建应使用实际时间;每种组合输出到新目录,并检查 HTML 正文。断言失败或命令超时会返回非零状态,请先查看报错,不要把这些开关直接加进发布脚本。
6. 常见问题
- 两个列表都没有目标文件:检查配置里的内容目录、语言、文件扩展名及排除规则;确认文件真的在本次构建读取的位置。
- 预览能看,部署后 404:核对部署目录、域名和实际响应内容,按 curl 网站检查教程 排查。
- 本地正常,自动构建缺文章:在构建日志记录
hugo version、配置文件和当前时间,对照 Hugo 自动部署教程 检查运行环境。
7. 总结
先比较内容列表,再检查全新目录中的 HTML,可以把发布条件、输出地址和首页展示问题分开定位。修正生效的元数据后,用普通构建和实际 HTTP 响应验证结果。保留自动化实验,升级 Hugo 或调整发布流程时就能重新检查这些行为。
- 原文作者:春江暮客
- 原文链接:https://www.bobobk.com/hugo-missing-post-draft-dates.html
- 版权声明:本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。