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

Golang Echo框架模板问题:不同路由内容不更新

Echo框架模板渲染内容不更新问题

问题现象

使用Golang Echo框架提供HTML模板服务时,首页(/)和关于页(/about)的标题能正常切换,但内容始终显示首页内容。访问/about时,标题变为“About | Application”,但页面主体内容还是首页的欢迎信息。

项目结构

/app
  /handlers
    home.go
    about.go
  /views
    renderer.go
  /templates
    /layouts
      main.html
    /pages
      index.html
      about.html
  main.go

代码实现

main.go

package main

import (
    "app/handlers"
    "app/views"

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    e := echo.New()

    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    e.Renderer = views.NewRenderer()

    e.GET("/", handlers.Home)
    e.GET("/about", handlers.About)

    e.Static("/static", "static")

    e.Logger.Fatal(e.Start(":8080"))
}

views/renderer.go

package views

import (
    "html/template"
    "io"
    "path/filepath"
    "strings"

    "github.com/labstack/echo/v4"
)

type TemplateRenderer struct {
    templates *template.Template
}

func NewRenderer() *TemplateRenderer {
    funcMap := template.FuncMap{
        "dict": func(values ...interface{}) map[string]interface{} {
            if len(values)%2 != 0 {
                panic("invalid dict call")
            }
            dict := make(map[string]interface{}, len(values)/2)
            for i := 0; i < len(values); i += 2 {
                key, ok := values[i].(string)
                if !ok {
                    panic("dict keys must be strings")
                }
                dict[key] = values[i+1]
            }
            return dict
        },
    }

    tmpl := template.New("").Funcs(funcMap)

    layoutGlob := filepath.Join("templates", "layouts", "*.html")
    pagesGlob := filepath.Join("templates", "pages", "*.html")

    tmpl = template.Must(tmpl.ParseGlob(layoutGlob))
    tmpl = template.Must(tmpl.ParseGlob(pagesGlob))

    normalizedTemplates := template.New("").Funcs(funcMap)
    for _, t := range tmpl.Templates() {
        if t.Tree == nil || t.Tree.Root == nil {
            continue
        }
        name := t.Name()
        if strings.HasPrefix(name, "pages/") {
            name = strings.TrimPrefix(name, "pages/")
        }
        name = strings.TrimSuffix(name, ".html")
        _, err := normalizedTemplates.New(name).Parse(t.Tree.Root.String())
        if err != nil {
            panic("Failed to normalize template names: " + err.Error())
        }
    }

    return &TemplateRenderer{
        templates: normalizedTemplates,
    }
}

func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    return t.templates.ExecuteTemplate(w, name, data)
}

handlers/home.go

package handlers

import (
    "net/http"

    "github.com/labstack/echo/v4"
)

func Home(c echo.Context) error {
    return c.Render(http.StatusOK, "index", map[string]interface{}{})
}

handlers/about.go

package handlers

import (
    "net/http"

    "github.com/labstack/echo/v4"
)

func About(c echo.Context) error {
    return c.Render(http.StatusOK, "about", map[string]interface{}{})
}

模板文件

templates/layouts/main.html

{{ define "layout" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ block "title" . }}{{ if .title }}{{ .title }} | Application{{ else }}Application{{ end }}{{ end }}</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <nav>
        <a href="/">Home</a> | <a href="/about">About</a>
    </nav>
    <main>
        {{ template "content" . }}
    </main>
</body>
</html>
{{ end }}

templates/pages/index.html

{{ template "layout" (dict "title" "Home") }}

{{ define "content" }}
    <h1>Welcome to the Home Page</h1>
    <p>This is a simple multi-page app with Echo.</p>
{{ end }}

templates/pages/about.html

{{ template "layout" (dict "title" "About") }}

{{ define "content" }}
    <h1>About</h1>
    <p>This is a simple multi-page app with Echo.</p>
{{ end }}

问题原因

Go标准库的模板系统中,同一模板集内的同名define块会被最后解析的版本覆盖:

  • index.html和about.html都定义了名为content的块,而filepath.Glob按字典序解析文件(about.html先于index.html),导致index.html的content块覆盖了about.html的。
  • 渲染about模板时,虽然传入的title参数正确,但layout中调用的{{ template "content" . }}实际使用的是被覆盖后的首页content块,因此内容显示错误。

解决方案

方案1:为每个页面的内容块使用唯一名称

修改模板文件,让每个页面的内容块名称与页面绑定,避免冲突:

修改templates/layouts/main.html

将固定调用的content块改为动态传入的名称:

{{ define "layout" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ block "title" . }}{{ if .title }}{{ .title }} | Application{{ else }}Application{{ end }}{{ end }}</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <nav>
        <a href="/">Home</a> | <a href="/about">About</a>
    </nav>
    <main>
        {{ template .contentBlock . }}
    </main>
</body>
</html>
{{ end }}

修改templates/pages/index.html

重命名内容块并传入对应名称:

{{ template "layout" (dict "title" "Home" "contentBlock" "index_content") }}

{{ define "index_content" }}
    <h1>Welcome to the Home Page</h1>
    <p>This is a simple multi-page app with Echo.</p>
{{ end }}

修改templates/pages/about.html

同理修改关于页:

{{ template "layout" (dict "title" "About" "contentBlock" "about_content") }}

{{ define "about_content" }}
    <h1>About</h1>
    <p>This is a simple multi-page app with Echo.</p>
{{ end }}

方案2:使用block覆盖布局中的内容块

利用Go模板的block特性,让页面模板直接覆盖布局中的content块:

修改templates/layouts/main.html

保持布局不变,确保content是可覆盖的block:

{{ define "layout" }}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ block "title" . }}{{ if .title }}{{ .title }} | Application{{ else }}Application{{ end }}{{ end }}</title>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <nav>
        <a href="/">Home</a> | <a href="/about">About</a>
    </nav>
    <main>
        {{ block "content" . }}{{ end }}
    </main>
</body>
</html>
{{ end }}

修改templates/pages/index.html

定义页面模板并嵌入布局,用block覆盖content:

{{ define "index" }}
{{ template "layout" (dict "title" "Home") }}
{{ end }}

{{ block "content" . }}
    <h1>Welcome to the Home Page</h1>
    <p>This is a simple multi-page app with Echo.</p>
{{ end }}

修改templates/pages/about.html

同理修改关于页:

{{ define "about" }}
{{ template "layout" (dict "title" "About") }}
{{ end }}

{{ block "content" . }}
    <h1>About</h1>
    <p>This is a simple multi-page app with Echo.</p>
{{ end }}

优化渲染器(可选)

原渲染器的模板归一化步骤可简化,直接解析所有模板:

func NewRenderer() *TemplateRenderer {
    funcMap := template.FuncMap{
        "dict": func(values ...interface{}) map[string]interface{} {
            if len(values)%2 != 0 {
                panic("invalid dict call")
            }
            dict := make(map[string]interface{}, len(values)/2)
            for i := 0; i < len(values); i += 2 {
                key, ok := values[i].(string)
                if !ok {
                    panic("dict keys must be strings")
                }
                dict[key] = values[i+1]
            }
            return dict
        },
    }

    tmpl := template.New("").Funcs(funcMap)
    tmpl = template.Must(tmpl.ParseGlob(filepath.Join("templates", "**", "*.html")))

    return &TemplateRenderer{
        templates: tmpl,
    }
}

验证

修改后重启服务,访问/和/about,标题和内容都会正确切换。

内容的提问来源于stack exchange,提问作者Ali Zain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 06:04:57