Views
Template Data And Helpers
Pass controller data to templates and use registered helper functions.
Set template data
Controllers pass data to templates with Set:
func (c *PostsController) Show(
_ http.ResponseWriter,
r *http.Request,
) error {
post, ok := c.posts.Get(r.PathValue("post_id"))
if !ok {
return lazycontroller.Error(http.StatusNotFound, fmt.Errorf("post not found"))
}
c.Set("title", post.Title)
c.Set("post", post)
return nil
}
The view reads those values by name:
<h1>{{.title}}</h1>
<article>{{.post.Body}}</article>
Defer expensive values
Use SetLater when a value is expensive and likely to be needed by the view:
comments := c.SetLater("comments", func() ([]Comment, error) {
return c.comments.ForPost(post.ID)
})
SetLater stores a lazycontroller.Valuer under the template key and starts
the loader immediately. The controller can still call comments.Value() when
it needs the result directly.
Use SetWhenNeeded when only some templates or partials will read the value:
c.SetWhenNeeded("related", func(ctx context.Context) ([]Post, error) {
return c.posts.Related(ctx, post.ID)
})
Templates read both forms by calling Value:
{{range .comments.Value}}
<p>{{.Body}}</p>
{{end}}
The loader runs once and the result or error is memoized for the request. If the loader returns an error, template rendering fails with that error.
Use built-in helpers
lazyapp registers route, asset, form, cache, and Turbo helpers:
<a href="{{path_for "posts"}}">Posts</a>
{{link_to "Posts" (path_for "posts") (attr "class" "nav-link")}}
{{link_to .post.Title (path_for "post" .post.Param) (data "turbo-frame" "post")}}
{{link_to "Posts" (path_for "posts") (unless_current)}}
{{stylesheet "/styles.css"}}
{{importmap "/assets/importmap.json"}}
{{ cache "featured" "post_card" .post }}
Helpers return ordinary strings or trusted fragments, depending on what the helper renders.
Use link_to when a template should render a complete anchor from a generated
route path. attr adds a normal HTML attribute, data adds a data-*
attribute, and unless_current renders only the escaped link text when the
current request points at the same destination.
Register app helpers
Application helpers are passed to lazyapp.New:
func RegisterHelpers() map[string]any {
return map[string]any{
"read_time": ReadTime,
}
}
Wire them during startup:
func App() *lazyapp.App {
return lazyapp.New(lazyapp.Config{
Name: "sample_app",
Drawer: Draw,
Public: app.Public,
Views: app.Views,
Dependencies: Dependencies,
Helpers: lazyapp.Helpers{helpers.RegisterHelpers()},
})
}
Then call the helper from a template:
<p>{{read_time .post.Body}} min read</p>
Add SEO metadata
lazyapp installs SEO helpers automatically. Configure application defaults
with a context-aware SEO function, normally in init/seo.go, and wire it
through lazyapp.Config.SEO:
func App() *lazyapp.App {
return lazyapp.New(lazyapp.Config{
Name: "sample_app",
Drawer: Draw,
Public: app.Public,
Views: app.Views,
Dependencies: Dependencies,
SEO: SEO,
})
}
func SEO(ctx context.Context) []lazyseo.Option {
return []lazyseo.Option{
lazyseo.SiteName("GoLazy"),
lazyseo.Description("A GoLazy application."),
lazyseo.Language("en"),
lazyseo.TwitterCardType("summary"),
}
}
lazyapp.New calls Dependencies before SEO, so SEO defaults can read
dependency-backed values from the app context.
Call {{seo_lang}} from the opening <html> tag and {{seo}} from the layout
<head>:
<html lang="{{seo_lang}}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{seo}}
</head>
</html>
Read SEO And Sitemaps for controller metadata,
structured data, robots.txt, and sitemap generation.
Use view variants
View variants are server-side template variants for the same route and format.
They use the Rails-style +variant segment after the format:
app/views/posts/show.svg.tpl
app/views/posts/show.svg+square.tpl
Rendering show as svg with variant square tries the square template first
and falls back to show.svg.tpl. Use this for generated image shapes such as a
default social image and a square social image. These are not SEO alternates;
SEO alternates are public URLs declared with c.Alternate(...).
Controllers can render that SVG content as a string:
svg, err := c.RenderSVGString("show", "square")
if err != nil {
return err
}
That gives image-generation code a normal Go string to write, rasterize, or
register as an asset before setting the public URL with c.SEOImage(...).