Skip to content
Trilha
Chapters

Learn

Nested layouts

One layout per folder, from the innermost to the outermost, and how the title travels between them.

A layout.go wraps every page in its folder and in the folders below. The layout in app/ is the root and is usually the only one that writes <html>.

The signature#

func Layout(c *trilha.Ctx, children h.Node) (h.Node, error)

children is the page already rendered as a node, or the innermost layout already applied. You decide where to place it.

A layout for the agenda#

Create app/events/layout.go:

package events

import (
	"github.com/emersonjoe/trilha"
	"github.com/emersonjoe/trilha/h"
)

func Layout(c *trilha.Ctx, children h.Node) (h.Node, error) {
	return h.Section(h.Class("agenda"),
		h.Nav(
			h.A(h.Href("/events"), h.Text("All")),
			h.A(h.Href("/events/new"), h.Text("New event")),
		),
		children,
	), nil
}

Now /events, /events/new and /events/anything appear inside that <section>, which in turn appears inside the <main> of the root layout.

app/…/page.go
// app/events/layout.go
func Layout(c *trilha.Ctx, children h.Node) (h.Node, error) {
	return h.Section(h.Class("agenda"),
		h.Nav(h.A(h.Href("/events"), h.Text("All")),
		      h.A(h.Href("/events/new"), h.Text("New"))),
		children,
	), nil
}

// app/events/page.go
func Page(c *trilha.Ctx) (h.Node, error) {
	c.SetTitle("Events")
	return h.H2(h.Text("Upcoming events")), nil
}
result

Upcoming events

Execution order#

For GET /events/go-meetup:

  1. app/events/slug_/page.goPage produces the page node.
  2. app/events/layout.go → receives that node as children.
  3. app/layout.go → receives the result of step 2.

Inside out. A folder without layout.go simply does not take part.

Title and other page data for the layout#

The page runs before the layouts. That is why c.SetTitle("Events") in the page works in the root layout, which reads c.Title() to build the <title>. The same goes for any value you store with c.Set(key, value) and read with c.Get(key).

// in the page
c.SetTitle("Go Meetup")
c.Set("description", "An evening of talks in Campinas")

// in the root layout
h.Title(h.Text(c.Title())),
h.Meta(h.Name("description"), h.Content(str(c.Get("description")))),

Layouts in route groups#

A group (organizer-/) may have a layout. It applies to the pages of the group and counts as one level in the order: page → group layout → root layout.

Challenge#

Make the layout of app/events/ show, below the navigation, a <p> with the title of the current page, to confirm that the title set in Page is already available there.

Show solution
func Layout(c *trilha.Ctx, children h.Node) (h.Node, error) {
	return h.Section(h.Class("agenda"),
		h.Nav(
			h.A(h.Href("/events"), h.Text("All")),
			h.A(h.Href("/events/new"), h.Text("New event")),
		),
		h.P(h.Class("crumb"), h.Text(c.Title())),
		children,
	), nil
}