Views
SEO And Sitemaps
Configure SEO defaults, page metadata, robots.txt, and sitemap.xml for public GoLazy apps.
Keep SEO opt-in
Generated GoLazy apps do not need SEO defaults, a control plane, or a sitemap to serve their first page. Add SEO when the app has public pages that should be indexed, shared, or represented with stable metadata.
lazyapp registers the {{seo}} and {{seo_lang}} helpers automatically.
Application-wide defaults are optional and come from lazyapp.Config.SEO.
Configure defaults in init/seo.go
lazyapp.Config.SEO is a function:
func App() *lazyapp.App {
return lazyapp.New(lazyapp.Config{
Name: "sample_app",
Drawer: Draw,
Public: app.Public,
Views: app.Views,
Dependencies: Dependencies,
SEO: SEO,
})
}
Define SEO in init/seo.go:
package appinit
import (
"context"
"golazy.dev/lazyseo"
)
func SEO(ctx context.Context) []lazyseo.Option {
return []lazyseo.Option{
lazyseo.SiteName("Example"),
lazyseo.Description("An example GoLazy application."),
lazyseo.Language("en"),
lazyseo.Locale("en_US"),
lazyseo.Type("website"),
lazyseo.TwitterCardType("summary"),
}
}
lazyapp.New runs Dependencies before it calls SEO, so this function can
read services or settings that were placed into the app context during
dependency initialization.
Render metadata in the layout
Call {{seo_lang}} from the opening <html> tag and {{seo}} from the
layout <head>:
<!doctype html>
<html lang="{{seo_lang}}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{seo}}
</head>
<body>{{.content}}</body>
</html>
When no defaults or request metadata are set, the helpers render empty values.
Set page metadata
Controllers set request-local metadata:
func (c *PostsController) Show(postID string) error {
post, ok := c.posts.Get(postID)
if !ok {
return lazycontroller.Error(http.StatusNotFound, fmt.Errorf("post not found"))
}
c.Metadata(post)
c.Alternate("de", "https://example.com/de/posts/" + post.Param)
c.Set("post", post)
return nil
}
Metadata reads small optional interfaces from the model. The minimum useful
interface is Title() string; common optional interfaces include
Description() string, Canonical() string, Image() string,
ImageAlt() string, Kind() lazyseo.PageKind,
PublishedTime() time.Time, LastUpdated() time.Time,
OpenGraph() lazyseo.OpenGraph, TwitterCard() lazyseo.TwitterCard, and
JSONLD() any. If the model has enough information and does not provide
explicit JSON-LD, GoLazy emits a conventional JSON-LD node automatically.
Use controller helpers to make page-specific edits after Metadata:
c.OpenGraph(lazyseo.OpenGraph{
Description: "Social preview description",
Image: "/posts/hello-og.png",
ImageAlt: "Hello post social preview",
ImageWidth: 1200,
ImageHeight: 630,
})
c.TwitterCard(lazyseo.TwitterCard{
Card: "summary_large_image",
Image: "/posts/hello-twitter.png",
ImageAlt: "Hello post social preview",
})
Kind accepts shared page-kind constants such as lazyseo.Article,
lazyseo.WebPage, lazyseo.WebSite, lazyseo.Product, and
lazyseo.Restaurant; the same value carries the Open Graph type and the
schema.org type name for automatic JSON-LD work. PublishedTime renders
article:published_time, and LastUpdated renders article:modified_time.
Canonical renders <link rel="canonical">. Alternate renders
<link rel="alternate" hreflang="...">. JSONLD renders an
application/ld+json script in the head.
Generate sitemaps
Sitemap generation is opt-in. With no sitemap config, GoLazy does not serve
/sitemap.xml and robots.txt does not advertise one.
Add sitemap entries in application startup:
func App() *lazyapp.App {
return lazyapp.New(lazyapp.Config{
Name: "sample_app",
Drawer: Draw,
Public: app.Public,
Views: app.Views,
Dependencies: Dependencies,
SEO: SEO,
Sitemap: lazyapp.SitemapConfig{
BaseURL: "https://example.com",
URLs: []lazyapp.SitemapURL{
{
Location: "/",
LastUpdated: time.Date(2026, 6, 20, 0, 0, 0, 0, time.UTC),
ChangeFreq: "weekly",
Priority: 1,
},
},
},
})
}
Use SitemapSourceFunc when URLs come from a service:
Sitemap: lazyapp.SitemapConfig{
BaseURL: "https://example.com",
Sources: []lazyapp.SitemapSource{
lazyapp.SitemapSourceFunc(func() ([]lazyapp.SitemapURL, error) {
return postSitemapURLs(), nil
}),
},
},
When sitemap generation is enabled, the default robots.txt automatically
adds the generated sitemap URL.
Configure robots
lazyapp serves permissive robots.txt by default:
User-agent: *
Allow: /
Configure robots explicitly when needed:
Robots: lazyapp.RobotsConfig{
Rules: []lazyapp.RobotsRule{{
UserAgent: "*",
Disallow: []string{"/admin"},
}},
},
Set Robots.Disabled or Sitemap.Disabled when the generated file should not
be served. The newest sitemap LastUpdated value is also used as the generated
Last-Modified header for cache validation.