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.)
Ordering of the templates is important when you parse them. Try this:
<!DOCTYPE HTML>
...
{{template "title" .}}
...
{{template "content" .}}}
...
{{define "title"}}{{end}}
{{define "content"}}{{end}}
page1:
{{define "title"}}Title1 contents{{end}}
{{define "content"}}Page 1 Contents{{end}}
page2:
{{define "title"}}Title2 contents{{end}}
{{define "content"}}Page 2 Contents{{end}}
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))
This way each template will include the layout and the corresponding page definitions.