Templates & Partials
Gozzi uses Go's standard html/template package for rendering HTML, providing a powerful and flexible templating system. Your layouts and partials live in the templates/ folder and define how each page or section of your site should look.
Table of Contents
- Template Structure
- Development Server Features
- Template Mapping
- Template Variables
- Partials & Macros
- Template Functions
- Advanced Patterns
- Examples
Template Structure
Basic Template Organization
templates/
├── home.html # Homepage layout
├── blog.html # Blog section list layout
├── post.html # Blog post (single page)
├── notes.html # Notes list
├── note.html # Single note
├── prose.html # General prose pages
├── tags.html # All tags index
├── tag.html # Posts under a specific tag
├── 404.html # Custom 404 page
├── partials/ # Shared snippets
│ ├── _head.html
│ ├── _header.html
│ ├── _footer.html
│ ├── _scripts.html
│ ├── _toc.html # Table of contents
│ ├── _word_count.html # Reading time
│ ├── _sharing.html # Social sharing
│ ├── _comment.html # Comments section
│ └── _reaction.html # Reaction buttons
└── macros/ # Reusable components
├── alert.html
├── codeblock.html
├── important.html
├── mermaid.html
├── pagination.html
└── warning.htmlTemplate Hierarchy
Templates follow an inheritance pattern:
- Base templates define overall structure
- Section templates handle list pages
- Page templates render individual content
- Partials provide reusable components
- Macros offer advanced functionality
Development Server Features
🔥 Live Template Reloading
When running gozzi serve, the development server provides instant template updates:
# Start development server
gozzi serve --port 1313
# Edit any template file:
# - templates/*.html
# - templates/partials/*.html
# - templates/macros/*.html
# Browser automatically refreshes with changes!How Live Reload Works
- File Watching: Server monitors
templates/directory for changes - Template Reloading: Automatically reloads template cache on file changes
- Smart Rebuilding: Only regenerates affected pages
- Browser Refresh: Sends refresh signal via Server-Sent Events (SSE)
Watched File Types
The development server watches these file extensions in templates/:
.html- Template files.css- Stylesheets (in static folder).js- JavaScript files (in static folder).md- Content filesconfig.toml- Configuration changes
Development Context Variables
During development, templates have access to additional context:
<!-- Development server info (only available during serve) -->
{{ if .IsDev }}
<div id="dev-info">
<p>Development Mode Active</p>
<p>Port: {{ .DevPort }}</p>
<p>Live Reload: Enabled</p>
</div>
{{ end }}
<!-- Live reload script automatically injected -->
<!-- No need to manually add this during development -->Template Mapping
Content-to-Template Mapping
Gozzi maps content to templates based on content type and location:
| Content | Template Used | Output Path | Description |
|---|---|---|---|
content/_index.md | home.html | /index.html | Site homepage |
content/blog/_index.md | blog.html | /blog/index.html | Blog section list |
content/blog/post/index.md | post.html | /blog/post/index.html | Individual blog post |
content/notes/_index.md | notes.html | /notes/index.html | Notes section list |
content/notes/note/index.md | note.html | /notes/note/index.html | Individual note |
content/about/index.md | prose.html | /about/index.html | General page |
Template Resolution Order
When selecting templates, Gozzi follows this priority:
- Front matter override:
template = "custom.html"in front matter - Section-specific:
{section}.html(e.g.,blog.html) - Content type:
post.html,note.html,prose.html - Default fallback:
home.htmlfor root,prose.htmlfor pages
Template Variables
Each template receives a context object (.) with comprehensive site and content data.
.Site - Global Configuration
Site-wide data from config.toml:
<!-- Basic site info -->
<title>{{ .Site.Config.title }}</title>
<meta name="description" content="{{ .Site.Config.description }}">
<link rel="canonical" href="{{ .Site.Config.base_url }}">
<!-- Custom configuration -->
<p>{{ .Site.Config.extra.bio }}</p>
<img src="{{ .Site.Config.extra.avatar }}" alt="Profile">
<!-- Navigation data -->
{{ range .Site.Config.extra.sections }}
<a href="{{ .path }}">{{ .name }}</a>
{{ end }}
<!-- Social links -->
{{ range .Site.Config.extra.links }}
<a href="{{ .url }}">
<img src="/icon/{{ .icon }}.svg" alt="{{ .name }}">
</a>
{{ end }}.Section - Section Data
Available in section templates (list pages with _index.md):
<!-- Section metadata -->
<h1>{{ .Section.Config.title }}</h1>
<p>{{ .Section.Config.description }}</p>
<!-- Section content -->
{{ .Section.Content }}
<!-- Custom section data -->
{{ if .Section.Config.extra.hero_text }}
<div class="hero">{{ .Section.Config.extra.hero_text }}</div>
{{ end }}
<!-- List all children pages -->
{{ range .Section.Children }}
<article>
<h2><a href="{{ .Permalink }}">{{ .Config.title }}</a></h2>
<time>{{ date .Config.date "January 2, 2006" }}</time>
<p>{{ .Config.description }}</p>
{{ if .Config.featured }}
<span class="featured">Featured</span>
{{ end }}
<div class="tags">
{{ range .Config.tags }}
<span class="tag">#{{ . }}</span>
{{ end }}
</div>
</article>
{{ end }}.Page - Individual Page Data
Used in page templates for individual content:
<!-- Page metadata -->
<h1>{{ .Page.Config.title }}</h1>
<meta name="description" content="{{ .Page.Config.description }}">
<!-- Dates and timing -->
<time datetime="{{ .Page.Config.date.Format "2006-01-02" }}">
{{ date .Page.Config.date "January 2, 2006" }}
</time>
{{ if not .Page.Config.updated.IsZero }}
<p>Updated: {{ date .Page.Config.updated "January 2, 2006" }}</p>
{{ end }}
<!-- Content and metadata -->
{{ .Page.Content }}
<p>Permalink: {{ .Page.Permalink }}</p>
<!-- Navigation -->
{{ with .Page.Higher }}
<a href="{{ .Permalink }}">← {{ .Config.title }}</a>
{{ end }}
{{ with .Page.Lower }}
<a href="{{ .Permalink }}">{{ .Config.title }} →</a>
{{ end }}
<!-- Custom page data -->
{{ if .Page.Config.extra.toc }}
{{ template "partials/_toc.html" . }}
{{ end }}
<!-- Tags and categorization -->
{{ range .Page.Config.tags }}
<a href="/tags/{{ . | urlize }}">#{{ . }}</a>
{{ end }}Priority Function for Inheritance
Use the priority function to cascade values from page → section → site:
<!-- Use page title, fall back to section, then site -->
<title>{{ priority .Page.Config.title .Section.Config.title .Site.Config.title }}</title>
<!-- Language inheritance -->
<html lang="{{ priority .Page.Config.lang .Section.Config.lang .Site.Config.lang }}">
<!-- Description with fallbacks -->
{{ $description := priority .Page.Config.description .Section.Config.description .Site.Config.description }}
<meta name="description" content="{{ $description }}">Partials & Macros
Partials - Reusable Components
Partials are shared template snippets that promote DRY (Don't Repeat Yourself) principles:
Including Partials
<!-- Basic inclusion with current context -->
{{ template "partials/_head.html" . }}
{{ template "partials/_header.html" . }}
{{ template "partials/_footer.html" . }}
<!-- Pass custom data to partial -->
{{ template "partials/_pagination.html" (dict "pages" .Section.Children "current" .Page) }}Common Partials
_head.html - Document Head:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ priority .Page.Config.title .Section.Config.title .Site.Config.title }}</title>
<!-- SEO Meta Tags -->
{{ $description := priority .Page.Config.description .Section.Config.description .Site.Config.description }}
<meta name="description" content="{{ $description }}">
<!-- Open Graph -->
<meta property="og:title" content="{{ .Page.Config.title }}">
<meta property="og:description" content="{{ $description }}">
{{ if .Page.Config.img }}
<meta property="og:image" content="{{ .Page.Config.img_url }}">
{{ end }}
<!-- Canonical URL -->
<link rel="canonical" href="{{ .Page.URL }}">
<!-- Stylesheets -->
<link rel="stylesheet" href="/css/main.css">
</head>_toc.html - Table of Contents:
{{ if .Page.Config.extra.toc }}
<nav class="toc">
<h3>Table of Contents</h3>
<!-- TOC generated from page content -->
{{ .Page.TOC }}
</nav>
{{ end }}_sharing.html - Social Sharing:
<div class="sharing">
<h4>Share this post</h4>
{{ $url := .Page.Permalink }}
{{ $title := .Page.Config.title }}
<a href="https://twitter.com/intent/tweet?url={{ $url }}&text={{ $title }}" target="_blank">
Twitter
</a>
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ $url }}" target="_blank">
LinkedIn
</a>
</div>Macros - Advanced Components
Macros provide complex, reusable functionality:
Alert Macro (macros/alert.html):
<!-- Usage: {{ template "macros/alert.html" (dict "type" "info" "message" "Important info!") }} -->
<div class="alert alert-{{ .type }}">
{{ if eq .type "warning" }}⚠️{{ end }}
{{ if eq .type "info" }}ℹ️{{ end }}
{{ if eq .type "success" }}✅{{ end }}
{{ .message }}
</div>Code Block Macro (macros/codeblock.html):
<!-- Enhanced code blocks with copy functionality -->
<div class="codeblock">
<div class="codeblock-header">
<span class="language">{{ .lang }}</span>
<button class="copy-btn" data-clipboard-text="{{ .code }}">Copy</button>
</div>
<pre><code class="language-{{ .lang }}">{{ .code }}</code></pre>
</div>Template Functions
Gozzi provides extensive template functions beyond Go's built-ins.
Content Functions
<!-- Get section data -->
{{ $blog := get_section "blog" }}
{{ range limit 5 (reverse $blog.Children) }}
<a href="{{ .Permalink }}">{{ .Config.title }}</a>
{{ end }}
<!-- Render markdown inline -->
{{ $markdown := "**Bold text** and _italic_" }}
{{ markdown $markdown }}
<!-- Load external files -->
{{ $svg := load "static/icon/arrow.svg" }}
{{ $svg }}String Functions
<!-- URL generation -->
{{ $slug := "My Great Post Title" | urlize }}
<!-- Output: my-great-post-title -->
<!-- String manipulation -->
{{ "hello world" | upper }} <!-- HELLO WORLD -->
{{ " trim me " | trim }} <!-- trim me -->
{{ "one,two,three" | split "," }} <!-- ["one", "two", "three"] -->
<!-- Contains and prefix checking -->
{{ if contains .Page.Config.tags "go" }}
<span class="go-post">Go Related</span>
{{ end }}
{{ if has_prefix .Page.URL "/blog/" }}
<nav>Blog Navigation</nav>
{{ end }}Date Functions
<!-- Format dates -->
{{ date .Page.Config.date "January 2, 2006" }}
{{ date .Page.Config.date "2006-01-02" }}
{{ date .Page.Config.date "Jan 2" }}
<!-- Current time -->
{{ $now := now }}
{{ date $now "2006-01-02 15:04:05" }}
<!-- Parse date strings -->
{{ $parsed := to_date "2025-01-15" }}
{{ date $parsed "January 2, 2006" }}Collection Functions
<!-- Pagination -->
{{ $pages := limit 10 .Section.Children }}
{{ range $pages }}
<article>{{ .Config.title }}</article>
{{ end }}
<!-- Grouping -->
{{ $groups := group_by .Section.Children "Config.tags" }}
{{ range $groups }}
<h3>{{ .Key }}</h3>
{{ range .Items }}
<p>{{ .Config.title }}</p>
{{ end }}
{{ end }}
<!-- Filtering -->
{{ $featured := where .Section.Children "Config.featured" true }}
{{ range $featured }}
<div class="featured">{{ .Config.title }}</div>
{{ end }}
<!-- First and last -->
{{ $latest := first 3 (reverse .Section.Children) }}
{{ $oldest := last 3 .Section.Children }}Logic Functions
<!-- Conditionals -->
{{ if and .Page.Config.featured (eq .Page.Config.draft false) }}
<span class="featured-published">Featured & Published</span>
{{ end }}
{{ if or (eq .Section.Name "blog") (eq .Section.Name "notes") }}
<div class="content-section">Content</div>
{{ end }}
<!-- Comparisons -->
{{ if ne .Page.Config.template "home.html" }}
<nav>Secondary Navigation</nav>
{{ end }}
<!-- Default values -->
{{ $author := default "Anonymous" .Page.Config.extra.author }}
<p>By {{ $author }}</p>Asset Functions
<!-- Asset path generation -->
{{ asset "/css/main.css" . }} <!-- /css/main.css with cache busting -->
{{ asset "/js/app.js" . }} <!-- /js/app.js?v=hash -->
<!-- Load and process files -->
{{ $styles := load "static/css/critical.css" }}
<style>{{ $styles }}</style>
<!-- Load as HTML attribute -->
{{ $icon := load_attribute "static/icon/menu.svg" }}
<div data-icon="{{ $icon }}">Menu</div>Advanced Patterns
Template Inheritance
Create base templates for consistent structure:
base.html:
<!DOCTYPE html>
<html lang="{{ priority .Page.Config.lang .Section.Config.lang .Site.Config.lang }}">
{{ template "partials/_head.html" . }}
<body class="{{ block "body_class" . }}page{{ end }}">
{{ template "partials/_header.html" . }}
<main>
{{ block "content" . }}
<p>Default content</p>
{{ end }}
</main>
{{ template "partials/_footer.html" . }}
{{ template "partials/_scripts.html" . }}
</body>
</html>post.html (inherits from base):
{{ template "base.html" . }}
{{ define "body_class" }}post{{ end }}
{{ define "content" }}
<article class="prose">
<h1>{{ .Page.Config.title }}</h1>
<div class="meta">
<time>{{ date .Page.Config.date "January 2, 2006" }}</time>
{{ template "partials/_word_count.html" . }}
</div>
{{ .Page.Content }}
{{ template "partials/_pagination.html" . }}
</article>
{{ end }}Conditional Template Loading
<!-- Load different partials based on context -->
{{ if eq .Section.Name "blog" }}
{{ template "partials/_blog_sidebar.html" . }}
{{ else if eq .Section.Name "notes" }}
{{ template "partials/_notes_sidebar.html" . }}
{{ else }}
{{ template "partials/_default_sidebar.html" . }}
{{ end }}
<!-- Feature flags from config -->
{{ if .Site.Config.extra.enable_comments }}
{{ template "partials/_comments.html" . }}
{{ end }}
{{ if .Site.Config.extra.enable_analytics }}
{{ template "partials/_analytics.html" . }}
{{ end }}Data Processing
<!-- Complex data manipulation -->
{{ $posts := get_section "blog" }}
{{ $featured := where $posts.Children "Config.featured" true }}
{{ $recent := first 5 (reverse (where $posts.Children "Config.draft" false)) }}
<section class="featured">
<h2>Featured Posts</h2>
{{ range $featured }}
<article>
<h3><a href="{{ .Permalink }}">{{ .Config.title }}</a></h3>
<p>{{ .Config.description }}</p>
</article>
{{ end }}
</section>
<section class="recent">
<h2>Recent Posts</h2>
{{ range $recent }}
<article>
<h3><a href="{{ .Permalink }}">{{ .Config.title }}</a></h3>
<time>{{ date .Config.date "Jan 2" }}</time>
</article>
{{ end }}
</section>Examples
Complete Blog Template
blog.html - Blog section listing:
<!DOCTYPE html>
<html lang="{{ .Site.Config.lang }}">
{{ template "partials/_head.html" . }}
<body class="blog-list">
{{ template "partials/_header.html" . }}
<main>
<header class="section-header">
<h1>{{ .Section.Config.title }}</h1>
<p>{{ .Section.Config.description }}</p>
{{ if .Section.Config.extra.hero_text }}
<div class="hero">{{ .Section.Config.extra.hero_text }}</div>
{{ end }}
</header>
<div class="posts">
{{ $posts := where .Section.Children "Config.draft" false }}
{{ range $posts }}
<article class="post-card">
<h2><a href="{{ .Permalink }}">{{ .Config.title }}</a></h2>
<div class="meta">
<time>{{ date .Config.date "January 2, 2006" }}</time>
{{ if .Config.featured }}
<span class="featured">Featured</span>
{{ end }}
</div>
<p>{{ .Config.description }}</p>
{{ if .Config.tags }}
<div class="tags">
{{ range .Config.tags }}
<a href="/tags/{{ . | urlize }}" class="tag">#{{ . }}</a>
{{ end }}
</div>
{{ end }}
</article>
{{ end }}
</div>
{{ if gt (len $posts) 10 }}
{{ template "macros/pagination.html" (dict "posts" $posts "per_page" 10) }}
{{ end }}
</main>
{{ template "partials/_footer.html" . }}
</body>
</html>Advanced Post Template
post.html - Individual post with all features:
<!DOCTYPE html>
<html lang="{{ priority .Page.Config.lang .Section.Config.lang .Site.Config.lang }}">
{{ template "partials/_head.html" . }}
<body class="post">
{{ template "partials/_header.html" . }}
<div id="wrapper">
<aside class="sidebar">
{{ if .Page.Config.extra.toc }}
{{ template "partials/_toc.html" . }}
{{ end }}
<button id="back-to-top" aria-label="Back to top">
{{ $icon := load "static/icon/arrow-up.svg" }}
{{ $icon }}
</button>
</aside>
<main>
{{ template "partials/_copy_code.html" . }}
<article class="prose">
<header>
<h1>{{ .Page.Config.title }}</h1>
<div class="post-meta">
<time datetime="{{ .Page.Config.date.Format "2006-01-02" }}">
{{ date .Page.Config.date "January 2, 2006" }}
</time>
{{ if not .Page.Config.updated.IsZero }}
<span>Updated:
<time datetime="{{ .Page.Config.updated.Format "2006-01-02" }}">
{{ date .Page.Config.updated "January 2, 2006" }}
</time>
</span>
{{ end }}
{{ template "partials/_word_count.html" . }}
</div>
{{ if .Page.Config.tags }}
<div class="tags">
{{ range .Page.Config.tags }}
{{ $tag_url := printf "/tags/%s" (. | urlize) }}
<a href="{{ $tag_url }}" class="tag">#{{ . }}</a>
{{ end }}
</div>
{{ end }}
</header>
{{ template "partials/_outdate.html" . }}
<div class="content">
{{ .Page.Content }}
</div>
{{ pagination . }}
</article>
{{ template "partials/_sharing.html" . }}
{{ template "partials/_reaction.html" . }}
{{ template "partials/_comment.html" . }}
</main>
</div>
{{ template "partials/_footer.html" . }}
{{ template "partials/_scripts.html" . }}
</body>
</html>Dynamic Navigation
partials/_header.html:
<header class="site-header">
<div class="container">
<a href="/" class="site-title">{{ .Site.Config.title }}</a>
<nav class="main-nav">
{{ range .Site.Config.extra.sections }}
{{ $current := "" }}
{{ if eq .path $.Page.Section }}
{{ $current = "current" }}
{{ end }}
<a href="{{ .path }}" class="nav-link {{ $current }}">
{{ .name }}
</a>
{{ end }}
</nav>
<div class="social-links">
{{ range .Site.Config.extra.links }}
<a href="{{ .url }}" class="social-link" title="{{ .name }}">
{{ $icon_path := printf "static/icon/%s.svg" .icon }}
{{ load $icon_path }}
</a>
{{ end }}
</div>
</div>
</header>Output Directory Structure
After building, your site structure mirrors the content organization:
public/
├── index.html # Home page
├── blog/
│ ├── index.html # Blog section list
│ └── my-post/
│ └── index.html # Individual post
├── notes/
│ ├── index.html # Notes section list
│ └── my-note/
│ └── index.html # Individual note
├── tags/
│ ├── index.html # All tags
│ └── go/
│ └── index.html # Posts tagged "go"
├── css/
│ └── main.css # Stylesheets
├── js/
│ └── main.js # JavaScript
└── img/
└── avatar.webp # Images and assetsBest Practices
Performance Optimization
- Minimize template nesting to reduce compilation time
- Cache frequently used data in variables
- Use partials to avoid code duplication
- Preload critical assets in the head section
Development Workflow
- Start development server:
gozzi serve --port 1313 - Edit templates: Changes are instantly reflected
- Test across sections: Ensure templates work for all content types
- Validate HTML: Use browser dev tools to check output
- Build for production:
gozzi buildto generate final site
Security Considerations
- Use
safefunction carefully: Only for trusted HTML content - Escape user content: Let Go templates handle escaping automatically
- Validate external data: Check loaded files and user inputs
Next Steps
- Configuration - Configure site settings and front matter
- HTML Functions - Complete template function reference
- Content Structure - Organize your content effectively
- CLI Reference - Development server and build commands