Layouts and Partials

define names a piece of template, template runs one, and block does both at once, which is how one layout serves every page.

Syntax{{block "content" .}}default{{end}} and t.ExecuteTemplate(w, "base", data)

Every page on a site shares a shell: the head, the navigation, the footer. Copying that shell into each page file means editing every file when the footer changes. Templates avoid this by letting one template run another.

define and template

{{define "name"}} ... {{end}} gives a piece of template text a name. It prints nothing where it appears; it is a declaration. {{template "name" .}} runs the named template and puts its output in that place. The value after the name is the data that template receives, so dot inside it means that value.

A group of named templates is one *template.Template, called a set. ParseFiles and ParseFS load every file's definitions into the same set, and ExecuteTemplate(w, "base", data) then starts execution at whichever name you choose.

Shorthand and longhand

block, added in Go 1.6, is a define followed immediately by a template, written once:

{{block "content" .}}<p>default</p>{{end}}

{{define "content"}}<p>default</p>{{end}}
{{template "content" .}}

Both forms mean the same thing. The body of a block is a default, and a later define of that name replaces it, because a named template can be redefined by a following Parse. That is the whole layout mechanism: the base file blocks out "content", each page file defines it.

Partials

A partial is only a named template you run from several places: a card, a form field, a pagination bar. There is no separate feature for it. Keep one set per page, layout plus that page's file, so two pages defining "content" never land in the same set and overwrite each other.

Example

Example · go
package main

import (
	"html/template"
	"log"
	"os"
)

type data struct {
	Title string
	User  string
}

// The layout. block "content" both declares the hole and gives it a default.
const layoutHTML = `{{define "base"}}<html><body>
<h1>{{.Title}}</h1>
{{block "content" .}}<p>nothing here yet</p>
{{end}}</body></html>
{{end}}`

// One page. Redefining "content" in the same set replaces the default.
const pageHTML = `{{define "content"}}<p>Welcome, {{.User}}.</p>
{{end}}`

func main() {
	t := template.Must(template.New("base.html").Parse(layoutHTML))
	t = template.Must(t.Parse(pageHTML))

	// Execution starts at the layout, not at the page.
	if err := t.ExecuteTemplate(os.Stdout, "base", data{Title: "Home", User: "ada"}); err != nil {
		log.Fatal(err)
	}
}

// Output:
// <html><body>
// <h1>Home</h1>
// <p>Welcome, ada.</p>
// </body></html>

When to use it

  • Give every page the same head, nav and footer from one file.
  • Override only the main content area per page while reusing the shell.
  • Reuse one product-card partial on the listing page and the search results page.
  • Render a page into a buffer so a template error never ships half a response.

More examples

One set per page

Put base.html (holding {{define "base"}} with a {{block "content" .}}) and home.html (holding {{define "content"}}) in a templates directory, then curl http://localhost:8080/. Parsing every template file into one big set is the mistake this avoids: the last file parsed wins the name, and you get the wrong page body with no error at all.

Example · go
package main

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

// Each page gets its own set: the shared layout plus that page's file. Parsing
// them separately stops two pages both defining "content" in one set.
var pages = map[string]*template.Template{
	"home":  template.Must(template.ParseFiles("templates/base.html", "templates/home.html")),
	"about": template.Must(template.ParseFiles("templates/base.html", "templates/about.html")),
}

func render(w http.ResponseWriter, name string, data any) {
	t, ok := pages[name]
	if !ok {
		http.Error(w, "unknown page", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := t.ExecuteTemplate(w, "base", data); err != nil {
		log.Print(err)
	}
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
		render(w, "home", map[string]string{"Title": "Home"})
	})
	log.Fatal(http.ListenAndServe(":8080", mux))
}

Buffer the page before writing it

Layouts make this matter more, not less: the error usually comes from a field referenced deep inside a partial, long after the opening tags have gone out. Buffering costs one allocation per request and buys you a real error page. curl -i http://localhost:8080/ to see the status and body together.

Example · go
package main

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

const baseHTML = `{{define "base"}}<h1>{{.Title}}</h1>
<p>{{.Body}}</p>
{{end}}`

var t = template.Must(template.New("base.html").Parse(baseHTML))

type page struct {
	Title string
	Body  string
}

func handle(w http.ResponseWriter, r *http.Request) {
	// Render to memory first. Writing straight to w would send 200 and half a
	// page before a template error could be turned into a 500.
	var buf bytes.Buffer
	if err := t.ExecuteTemplate(&buf, "base", page{Title: "Home", Body: "hello"}); err != nil {
		log.Print(err)
		http.Error(w, "internal error", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if _, err := buf.WriteTo(w); err != nil {
		log.Print(err)
	}
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /", handle)
	log.Fatal(http.ListenAndServe(":8080", mux))
}

A partial used twice

Partials are worth it as soon as a piece of markup appears twice, because the duplicate is where the two copies drift apart. Note that each call passes a different value, not the whole form: a partial should take the smallest piece of data it needs, which also makes it reusable from a page with a different shape.

Example · go
package main

import (
	"html/template"
	"log"
	"os"
)

type field struct {
	Label string
	Name  string
}

type form struct {
	Email field
	Pass  field
}

// One partial, two calls. The value after the name becomes the partial's dot.
const formHTML = `{{define "field"}}<label>{{.Label}} <input name="{{.Name}}"></label>
{{end}}{{define "form"}}<form method="post">
{{template "field" .Email}}{{template "field" .Pass}}</form>
{{end}}`

func main() {
	t := template.Must(template.New("forms").Parse(formHTML))
	f := form{
		Email: field{Label: "Email", Name: "email"},
		Pass:  field{Label: "Password", Name: "password"},
	}
	if err := t.ExecuteTemplate(os.Stdout, "form", f); err != nil {
		log.Fatal(err)
	}
}

// Output:
// <form method="post">
// <label>Email <input name="email"></label>
// <label>Password <input name="password"></label>
// </form>

Discussion

  • Be the first to comment on this lesson.