htmlgotemplatespackage

Go Templates and Embedding: Content from One Template Overriding Another When Using Template Blocks


I have been trying to understand the html/template package in combination with the embed package in golang 1.23 but I am struggling to even get a simple base layout with two pages working. As detailed below I encounter some overwriting of content (at least I suspect it to be the issue) where one and the same html render is displayed for different routes.

The file structure of my minimal example is as follows:

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

- main.go
- go.mod

The server embeds the html files and serves them with the standard net/http package (1.23):

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))
}

Now the way I attempted to design the templates is the typical base layout which gets "filled" through sub-pages.

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 }}

And the two simple sub pages:

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 }}

Now what happens is that for http://localhost:8080/page1 and http://localhost:8080/page2 the template for page 2 is displayed, even if I switch the name string in the ExecuteTemplate function call for both routes.

I have been experimenting quite a lot with the templates of page 1/2 (shifting the block declarations in and out of the define, using the template action instead of the block action, etc.). I suspect I am missing some very crucial fundamentals when it comes to golang's template package or some side effects introduced by the embed package. Can you guys give me a hint?

(I would love to do this with the standard package, not temlp, etc.)


Solution

  • Ordering of the templates is important when you parse them. Try this:

    1. Define the layout with references to other templates:
    <!DOCTYPE HTML>
    ...
    {{template "title" .}}
    ...
    {{template "content" .}}}
    ...
    
    {{define "title"}}{{end}}
    {{define "content"}}{{end}}
    
    1. Define individual pages
    page1: 
    {{define "title"}}Title1 contents{{end}}
    {{define "content"}}Page 1 Contents{{end}}
    
    page2:
    {{define "title"}}Title2 contents{{end}}
    {{define "content"}}Page 2 Contents{{end}}
    
    1. Parse two templates, one for page1 and one for page2. Page1 should include layout and page1. Page2 should include layout and page2.
    page1Templ := template.Must(template.New("p1").Parse(layout))
    template.Must(page1Templ.Parse(page1))
    page2Templ := template.Must(template.New("p2").Parse(layout))
    template.Must(page2Templ.Parse(page2))
    
    1. Execute page1Templ for page 1, page2Templ for page 2.

    This way each template will include the layout and the corresponding page definitions.