SEO, Meta Tags & Sitemap
How goilerplate handles SEO, meta tags, social media previews, and dynamic sitemap generation
goilerplate includes a comprehensive SEO system built directly into the base template. It’s simple, effective, and easy to customize.
How It Works
The SEO system is implemented in internal/ui/layouts/base.templ using a clean, component-based approach:
// Pass SEO props to any page
templ Base(props ...SEOProps) {
// SEO meta tags are automatically included
if len(props) > 0 {
@seo(props[0])
}
}
SEO Props Structure
Each page can define its SEO metadata:
type SEOProps struct {
Title string // Page title (appended with app name)
Description string // Meta description for search engines
Path string // Current page path for canonical URL
}
Usage Example
In Your Page Template
// internal/ui/pages/blog.templ
templ BlogPost(post *model.BlogPost) {
@layouts.Base(layouts.SEOProps{
Title: post.Title,
Description: post.Description,
Path: "/blog/" + post.Slug,
}) {
<article>
<h1>{ post.Title }</h1>
// ... rest of your content
</article>
}
}
Dynamic Title Updates
For HTMX requests, titles are updated dynamically using out-of-band swaps:
@templ.Fragment("seo-title") {
<title id="page-title" hx-swap-oob="true">{ fullTitle }</title>
}
Meta Tags Included
The SEO component automatically generates:
Basic Meta Tags
<title>- Page title with app name<meta name="description">- Search engine description<meta name="author">- Application author<meta name="robots">- Search engine instructions<link rel="canonical">- Canonical URL to prevent duplicate content
OpenGraph Tags (Facebook, LinkedIn)
og:title- Social media titleog:description- Social media descriptionog:type- Content type (website)og:url- Page URLog:site_name- Application nameog:image- Preview image (1200x630)og:image:alt- Image alt text
Twitter Card Tags
twitter:card- Card type (summary_large_image)twitter:title- Tweet preview titletwitter:description- Tweet preview descriptiontwitter:image- Preview imagetwitter:image:alt- Image alt text
Mobile Optimization
<meta name="theme-color">- Browser theme color- Viewport meta tag for responsive design
Configuration
SEO configuration is automatically loaded from your environment variables via the config:
templ seo(props SEOProps) {
{{ cfg := ctxkeys.Config(ctx) }}
{{ baseURL := cfg.AppURL }} // From APP_URL env var
{{ appName := cfg.AppName }} // From APP_NAME env var
{{ appTagline := cfg.AppTagline }} // From APP_TAGLINE env var
}
Set these in your .env file:
APP_URL=https://yourdomain.com
APP_NAME=Your App Name
APP_TAGLINE=Your app tagline
Social Preview Image
Place your social media preview image at:
/assets/img/social-preview.png
Recommended dimensions:
- Size: 1200x630 pixels
- Format: PNG or JPG
- File size: Under 1MB
Best Practices
- Unique titles and descriptions for every page
- Keep descriptions 150-160 characters for optimal search display
- Always provide canonical URLs via the Path field
- Use descriptive titles instead of generic ones
Testing Your SEO
Validation tools:
Sitemap & Robots.txt
goilerplate includes a smart, dynamic sitemap generator and robots.txt configuration that automatically discovers your content.
Robots.txt
Located at /static/robots.txt:
User-agent: *
Allow: /
Sitemap: /sitemap.xml
Customizing Robots.txt:
User-agent: *
Allow: /
Disallow: /admin/
Disallow: /app/
Crawl-delay: 1
Sitemap: /sitemap.xml
Dynamic Sitemap
How URLs Are Discovered
The sitemap automatically includes:
- Static Routes - Defined in
publicRoutesslice - Blog Posts - All markdown files in
/content/blog/ - Blog Tags - Unique tags from all blog posts
- Documentation - All markdown files in
/content/docs/
Adding Static Routes
Edit the publicRoutes slice in /internal/service/sitemap.go:
var publicRoutes = []struct {
Path string
Priority string
ChangeFreq string
}{
{"/", "1.0", "daily"},
{"/blog", "0.8", "daily"},
{"/docs", "0.8", "weekly"},
{"/login", "0.3", "monthly"},
{"/register", "0.3", "monthly"},
// Add new routes here:
// {"/about", "0.7", "monthly"},
// {"/contact", "0.5", "monthly"},
}
Priority Guidelines
| Priority | Use Case |
|---|---|
| 1.0 | Homepage only |
| 0.8-0.9 | Primary content pages (blog index, docs) |
| 0.7-0.8 | Individual blog posts, main docs |
| 0.5-0.6 | Category/tag pages, nested docs |
| 0.3-0.4 | Utility pages (login, register) |
| 0.1-0.2 | Legal pages, rarely updated content |
Change Frequency Values
Valid changefreq values:
always- Changes every time accessedhourly- Updated hourlydaily- Updated dailyweekly- Updated weeklymonthly- Updated monthlyyearly- Updated yearlynever- Archived content
Protected Routes
Routes requiring authentication are automatically excluded from the sitemap.
// NOT in sitemap (requires auth)
mux.HandleFunc("GET /dashboard", middleware.RequireAuth(base.DashboardPage))
// IN sitemap (public)
mux.HandleFunc("GET /blog", blog.ListPosts)
Accessing Endpoints
- Robots.txt:
http://localhost:8090/robots.txt - Sitemap:
http://localhost:8090/sitemap.xml
Submitting to Search Engines
- Google: Search Console → Sitemaps → Submit
sitemap.xml - Bing: Webmaster Tools → Sitemaps → Submit
sitemap.xml
Performance
For typical sites (< 1,000 URLs):
- Generation time: ~5-10ms
- Memory usage: Minimal
- No caching needed
Consider caching if you have:
- More than 1,000 URLs
- High crawler traffic (> 100 requests/hour)
Sitemap Limits
According to the sitemap protocol:
- Maximum 50,000 URLs per sitemap
- Maximum 50MB uncompressed size
- Use sitemap index for larger sites
Why Dynamic Generation?
- ✅ Always current (no stale data)
- ✅ Less maintenance (no rebuild needed)
- ✅ Single source of truth (content drives sitemap)
- ✅ Good performance (fast enough for most sites)
- ✅ Simpler deployment (no build step)
Common Issues
Sitemap Not Updating:
The sitemap is generated dynamically, so it’s always current. If you don’t see new content:
- Check that the content file exists
- Verify it has valid frontmatter
- Ensure the blog/docs service can read it
URLs Missing from Sitemap:
Check if the route is:
- Listed in
publicRoutes(for static pages) - Not behind authentication middleware
- Has valid content (for blog/docs)