静态网站生成引擎需确保四点:模板须一次性ParseFiles加载所有依赖文件;Markdown内容须用goldmark预转为template.HTML;写文件前用os.MkdirAll创建嵌套目录;并发渲染须为每个goroutine提供独立模板实例。

直接上手写一个可用的静态网站生成引擎模块,核心不是重造 Hugo,而是把模板渲染、内容解析、文件写入这三块串通,且避开 Go template 最常踩的坑。
template.ParseFiles 必须一次性加载所有依赖模板
常见错误是:在 layout.html 里写了 {{template "header"}},但只传了 "layout.html" 给 ParseFiles(),结果运行时报 template: "header" is undefined。Go 模板不递归扫描引用,它只认你显式列出的文件。
- 把所有用到的模板文件路径一次性传进去:
template.ParseFiles("templates/layout.html", "templates/header.html", "templates/footer.html") - 如果模板分散在子目录,路径必须相对于当前工作目录(
os.Getwd()),不是源文件位置;建议统一用filepath.Join("templates", "layout.html")拼接 - 偷懒但可靠的方式:
globbed, _ := filepath.Glob("templates/**/*.html"); template.ParseFiles(globbed...),避免漏文件
Markdown 内容必须提前转 HTML,不能靠模板函数现场处理
text/template 和 html/template 都不解析 Markdown;直接 {{.Content}} 只会输出原始字符串,{{.Content | markdown}} 则报 function "markdown" not defined —— 因为你没注册这个函数,而且现场调用还慢。
- 用
goldmark在数据准备阶段就完成转换:md := goldmark.New(); htmlBytes, _ := md.Convert([]byte(raw), &bytes.Buffer{}) - 把结果转成
template.HTML类型:data.Content = template.HTML(htmlString),否则模板会自动转义<p> - 别在
FuncMap里注册耗时函数;如果真要加,确保返回类型是template.HTML,且内部不重复解析同一段内容
os.Create 不会自动创建父目录,嵌套路径要手动保障
想生成 public/posts/hello-world/index.html,直接 os.Create("public/posts/hello-world/index.html") 会失败,报 no such file or directory —— 因为 posts/hello-world/ 这两级目录不存在。
立即学习“go语言免费学习笔记(深入)”;
- 写文件前先建目录:
os.MkdirAll(filepath.Dir(outPath), 0755) -
outPath建议用filepath.Join("public", "posts", "hello-world", "index.html")拼接,跨平台安全 - 不要依赖当前工作目录是否在项目根;构建脚本可能从任意路径执行,
os.Getwd()不稳定
并发执行 Execute 会 panic:concurrent map iteration
多个 goroutine 同时调用同一个 *template.Template 实例的 Execute(),会触发 panic: concurrent map iteration and map write —— 因为模板内部用了未加锁的 map 缓存。
- 每个 goroutine 必须用独立的
*template.Template实例;最简单做法是每次循环都重新ParseFiles() - 或者用
template.Clone()复制一份再用:t2, _ := t.Clone(); t2.Execute(...) - 别图省事共享一个模板实例去并发渲染;这不是性能瓶颈,是必现 crash
真正难的不是写代码,而是路径怎么算、模板怎么拆、内容何时转、目录何时建——这些细节不卡死,生成器就跑不起来。其他都可以慢慢加,但上面四点漏一个,连首页都吐不出来。


















