You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go模板与嵌入:使用模板块时内容被覆盖的问题排查

问题:Go 1.23 html/template与embed结合时模板内容覆盖问题

尝试用Go 1.23的html/template和embed包实现基础布局+两个页面的结构,但访问/page1和/page2时均显示Page 2的内容,怀疑存在模板内容覆盖的情况。

文件结构

- templates/
    - layout.html
    - page1.html
    - page2.html

- main.go
- go.mod

服务器代码(main.go)

package main

import (
    "embed"
    "html/template"
    "log"
    "net/http"
)

//go:embed templates/*.html
var templateFS embed.FS

var templates *template.Template

func main() {
    var err error

    templates, err = template.ParseFS(templateFS, "templates/*.html")
    if err != nil {
        log.Fatal("Error parsing templates: ", err)
    }

    http.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
        http.Redirect(w, r, "/page1", http.StatusSeeOther)
    })

    http.HandleFunc("GET /page1", func(w http.ResponseWriter, r *http.Request) {
        err := templates.ExecuteTemplate(w, "page1", nil)
        if err != nil {
            http.Error(w, "Template rendering error: "+err.Error(), http.StatusInternalServerError)
        }
    })

    http.HandleFunc("GET /page2", func(w http.ResponseWriter, r *http.Request) {
        err := templates.ExecuteTemplate(w, "page2", nil)
        if err != nil {
            http.Error(w, "Template rendering error: "+err.Error(), http.StatusInternalServerError)
        }
    })

    log.Println("Server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

模板文件

layout.html

{{ define "layout" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{ block "title" . }}My Website{{ end }}</title>
</head>
<body>
    <header>
        <h1>Welcome!</h1>
        <nav>
            <a href="/page1">Page 1</a> | 
            <a href="/page2">Page 2</a>
        </nav>
    </header>
    <main>
        {{ block "content" . }}{{ end }}
    </main>
</body>
</html>
{{ end }}

page1.html

{{ define "page1"}}
{{ template "layout" . }}
{{ end }}

{{ block "title" . }}Page 1{{ end }}

{{ block "content" . }}
    <h2>This is Page 1</h2>
    <p>Content for page 1 goes here.</p>
{{ end }}

page2.html

{{ define "page2"}}
{{ template "layout" . }}
{{ end }}

{{ block "title" . }}Page 2{{ end }}

{{ block "content" . }}
    <h2>This is Page 2</h2>
    <p>Content for page 2 goes here.</p>
{{ end }}

问题原因

Go的html/template在解析多个模板文件时,同名的define或block会被最后解析的模板覆盖。你的page1.html和page2.html都定义了全局的title和content块,而ParseFS按文件名顺序解析(通常page2.html在page1.html之后),因此page2的块会覆盖page1的,导致无论渲染哪个页面,都会使用page2的内容。

标准库解决方法

方法一:为每个页面的块设置唯一名称,动态调用

  1. 修改layout.html,改为动态调用传入的块名称:
{{ define "layout" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{ template .TitleBlock . }}</title>
</head>
<body>
    <header>
        <h1>Welcome!</h1>
        <nav>
            <a href="/page1">Page 1</a> | 
            <a href="/page2">Page 2</a>
        </nav>
    </header>
    <main>
        {{ template .ContentBlock . }}
    </main>
</body>
</html>
{{ end }}
  1. 修改page1.html,定义唯一命名的块:
{{ define "page1" }}
{{ template "layout" map "TitleBlock" "page1-title" "ContentBlock" "page1-content" }}
{{ end }}

{{ define "page1-title" }}Page 1{{ end }}

{{ define "page1-content" }}
    <h2>This is Page 1</h2>
    <p>Content for page 1 goes here.</p>
{{ end }}
  1. 修改page2.html同理:
{{ define "page2" }}
{{ template "layout" map "TitleBlock" "page2-title" "ContentBlock" "page2-content" }}
{{ end }}

{{ define "page2-title" }}Page 2{{ end }}

{{ define "page2-content" }}
    <h2>This is Page 2</h2>
    <p>Content for page 2 goes here.</p>
{{ end }}

方法二:在页面主模板内部嵌套block

利用Go模板的执行顺序,将页面的title和content块嵌套在主模板的define内部,这样执行该页面模板时,内部块会优先于全局同名块生效:

  1. 保持layout.html不变

  2. 修改page1.html:

{{ define "page1" }}
    {{ block "title" . }}Page 1{{ end }}
    {{ block "content" . }}
        <h2>This is Page 1</h2>
        <p>Content for page 1 goes here.</p>
    {{ end }}
    {{ template "layout" . }}
{{ end }}
  1. 修改page2.html:
{{ define "page2" }}
    {{ block "title" . }}Page 2{{ end }}
    {{ block "content" . }}
        <h2>This is Page 2</h2>
        <p>Content for page 2 goes here.</p>
    {{ end }}
    {{ template "layout" . }}
{{ end }}

注意:必须先定义block再调用layout,因为模板执行是顺序执行的,先定义的块会在调用layout时生效。

验证

两种方法都不需要修改原main.go代码,保持现有的ExecuteTemplate调用即可,访问/page1和/page2将分别显示对应页面的内容。

内容的提问来源于stack exchange,提问作者0xmichael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:40:57