Gin Multiple Templates
Introduction
When building web applications with the Gin framework in Go, you'll often need more than just a single HTML template. As your application grows, you might want to structure your frontend with layouts, partials, and various page templates. Gin provides a powerful way to work with multiple templates, allowing you to create modular and maintainable web interfaces.
In this guide, we'll learn how to:
- Set up multiple templates in a Gin application
 - Create and use layouts with template inheritance
 - Organize templates in different directories
 - Pass data to multiple templates
 - Handle template rendering with various strategies
 
Understanding Multiple Templates in Gin
By default, Gin uses the standard Go template engine. While you can load a single template with router.LoadHTMLGlob() or router.LoadHTMLFiles(), more complex applications benefit from using multiple templates with inheritance and includes.
Why Use Multiple Templates?
- Code Reusability: Define common elements like headers and footers once
 - Separation of Concerns: Keep different page content in separate files
 - Maintainability: Easier to update specific parts of your UI
 - Organization: Better structure for complex web applications
 
Basic Setup for Multiple Templates
Let's start with a basic setup for handling multiple templates in a Gin application:
package main
import (
	"github.com/gin-gonic/gin"
	"html/template"
	"net/http"
)
func main() {
	router := gin.Default()
	
	// Load templates from different directories
	router.LoadHTMLGlob("templates/**/*")
	
	router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"title": "Home Page",
		})
	})
	
	router.GET("/about", func(c *gin.Context) {
		c.HTML(http.StatusOK, "about.tmpl", gin.H{
			"title": "About Page",
		})
	})
	
	router.Run(":8080")
}
In this example, we're using LoadHTMLGlob("templates/**/*") which loads all templates from any subdirectory within the "templates" directory.
Organizing Your Templates
A common structure for templates in a web application might look like:
templates/
├── layouts/
│   └── base.tmpl
├── partials/
│   ├── header.tmpl
│   └── footer.tmpl
└── pages/
    ├── index.tmpl
    └── about.tmpl
Creating a Base Layout
Let's create a base layout template (templates/layouts/base.tmpl):
<!DOCTYPE html>
<html>
<head>
    <title>{{ .title }}</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
    <header>
        {{ template "header.tmpl" . }}
    </header>
    
    <main>
        {{ template "content" . }}
    </main>
    
    <footer>
        {{ template "footer.tmpl" . }}
    </footer>
    
    <script src="/static/js/main.js"></script>
</body>
</html>
Creating Partial Templates
Now, let's create header and footer partials (templates/partials/header.tmpl):
<nav>
    <div class="logo">My Gin App</div>
    <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
        <li><a href="/contact">Contact</a></li>
    </ul>
</nav>
And the footer (templates/partials/footer.tmpl):
<div class="footer-content">
    <p>© {{ .year }} My Gin Application. All rights reserved.</p>
</div>
Creating Page Templates
Now, for our actual pages (templates/pages/index.tmpl):
{{ define "content" }}
<div class="container">
    <h1>{{ .title }}</h1>
    <p>Welcome to our Gin application using multiple templates!</p>
    
    <div class="features">
        <div class="feature">
            <h3>Fast</h3>
            <p>Gin is one of the fastest Go web frameworks available.</p>
        </div>
        <div class="feature">
            <h3>Flexible</h3>
            <p>Create complex web applications with ease.</p>
        </div>
    </div>
</div>
{{ end }}
And the about page (templates/pages/about.tmpl):
{{ define "content" }}
<div class="container">
    <h1>{{ .title }}</h1>
    <p>This application demonstrates how to use multiple templates in Gin.</p>
    
    <div class="about-content">
        <h2>Our Story</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce at diam tellus.</p>
    </div>
</div>
{{ end }}
Loading Templates with Custom Functions
For more complex applications, you might want to create a custom template loader with functions:
package main
import (
	"github.com/gin-gonic/gin"
	"html/template"
	"net/http"
	"time"
)
func main() {
	router := gin.Default()
	
	// Create a template with custom functions
	tmpl := template.Must(template.New("").
		Funcs(template.FuncMap{
			"formatDate": func(t time.Time) string {
				return t.Format("Jan 02, 2006")
			},
			"safeHTML": func(s string) template.HTML {
				return template.HTML(s)
			},
		}).
		ParseGlob("templates/layouts/*.tmpl"))
	
	// Load partials
	template.Must(tmpl.ParseGlob("templates/partials/*.tmpl"))
	
	// Load page templates
	template.Must(tmpl.ParseGlob("templates/pages/*.tmpl"))
	
	// Set the template to Gin
	router.SetHTMLTemplate(tmpl)
	
	router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "base.tmpl", gin.H{
			"title": "Home Page",
			"year":  time.Now().Year(),
			"content": "index.tmpl",
		})
	})
	
	router.GET("/about", func(c *gin.Context) {
		c.HTML(http.StatusOK, "base.tmpl", gin.H{
			"title": "About Us",
			"year":  time.Now().Year(),
			"content": "about.tmpl",
		})
	})
	
	router.Run(":8080")
}
Advanced Template Rendering
Using Template Inheritance with Blocks
Another approach is to use block-based inheritance. Update your templates to use this pattern:
base.tmpl:
{{ define "base" }}
<!DOCTYPE html>
<html>
<head>
    <title>{{ block "title" . }}Default Title{{ end }}</title>
    <meta charset="utf-8">
    <link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
    <header>{{ block "header" . }}{{ end }}</header>
    <main>{{ block "content" . }}{{ end }}</main>
    <footer>{{ block "footer" . }}{{ end }}</footer>
</body>
</html>
{{ end }}
index.tmpl:
{{ define "index" }}
    {{ template "base" . }}
{{ end }}
{{ define "title" }}Home Page{{ end }}
{{ define "header" }}
    <nav>Home | <a href="/about">About</a></nav>
{{ end }}
{{ define "content" }}
    <h1>Welcome to the Home Page</h1>
    <p>This is the main content of our home page.</p>
{{ end }}
{{ define "footer" }}
    <p>© {{ .year }} My Application</p>
{{ end }}
And then in your Go code:
router.GET("/", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index", gin.H{
        "year": time.Now().Year(),
    })
})
Real-world Example: A Blog Platform
Let's create a more comprehensive example of a simple blog platform with multiple templates:
Directory Structure
templates/
├── layouts/
│   └── main.tmpl
├── partials/
│   ├── header.tmpl
│   ├── footer.tmpl
│   └── sidebar.tmpl
└── pages/
    ├── home.tmpl
    ├── post.tmpl
    └── author.tmpl
Sample Code for Blog
package main
import (
	"github.com/gin-gonic/gin"
	"html/template"
	"net/http"
	"time"
)
// Post represents a blog post
type Post struct {
	ID        int
	Title     string
	Content   string
	CreatedAt time.Time
	Author    string
}
// PostData returns sample blog posts
func PostData() []Post {
	return []Post{
		{
			ID:        1,
			Title:     "Getting Started with Gin",
			Content:   "Gin is a web framework written in Go...",
			CreatedAt: time.Now().AddDate(0, 0, -2),
			Author:    "Jane Doe",
		},
		{
			ID:        2,
			Title:     "Working with Templates",
			Content:   "Templates in Gin follow the standard Go template syntax...",
			CreatedAt: time.Now().AddDate(0, 0, -1),
			Author:    "John Smith",
		},
	}
}
func main() {
	router := gin.Default()
	
	// Custom template functions
	funcMap := template.FuncMap{
		"formatDate": func(t time.Time) string {
			return t.Format("January 2, 2006")
		},
		"excerpt": func(s string) string {
			if len(s) > 100 {
				return s[:97] + "..."
			}
			return s
		},
	}
	
	// Load all templates with our custom functions
	tmpl := template.Must(template.New("").Funcs(funcMap).ParseGlob("templates/layouts/*.tmpl"))
	template.Must(tmpl.ParseGlob("templates/partials/*.tmpl"))
	template.Must(tmpl.ParseGlob("templates/pages/*.tmpl"))
	
	router.SetHTMLTemplate(tmpl)
	
	// Serve static files
	router.Static("/static", "./static")
	
	// Routes
	router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "main.tmpl", gin.H{
			"title":       "Blog Home",
			"description": "Latest articles about Gin framework",
			"posts":       PostData(),
			"page":        "home.tmpl",
			"year":        time.Now().Year(),
		})
	})
	
	router.GET("/post/:id", func(c *gin.Context) {
		id := c.Param("id")
		var post Post
		
		// In a real app, you'd fetch this from a database
		for _, p := range PostData() {
			if p.ID == 1 { // Just for example, using ID=1
				post = p
				break
			}
		}
		
		c.HTML(http.StatusOK, "main.tmpl", gin.H{
			"title":       post.Title,
			"description": "Read the full article",
			"post":        post,
			"page":        "post.tmpl",
			"year":        time.Now().Year(),
		})
	})
	
	router.Run(":8080")
}
Our layout template (templates/layouts/main.tmpl):
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ .title }} | My Blog</title>
    <meta name="description" content="{{ .description }}">
    <link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
    {{ template "header.tmpl" . }}
    
    <div class="container">
        <div class="row">
            <div class="col-md-8">
                {{ template .page . }}
            </div>
            <div class="col-md-4">
                {{ template "sidebar.tmpl" . }}
            </div>
        </div>
    </div>
    
    {{ template "footer.tmpl" . }}
    
    <script src="/static/js/main.js"></script>
</body>
</html>
And the home page template (templates/pages/home.tmpl):
<h1>Latest Blog Posts</h1>
<div class="posts">
    {{ range .posts }}
    <article class="post-summary">
        <h2><a href="/post/{{ .ID }}">{{ .Title }}</a></h2>
        <p class="meta">By {{ .Author }} on {{ formatDate .CreatedAt }}</p>
        <p>{{ excerpt .Content }}</p>
        <a href="/post/{{ .ID }}" class="read-more">Read More</a>
    </article>
    {{ end }}
</div>
Advanced Techniques
Caching Templates in Production
In production environments, it's a good practice to cache your templates to avoid reloading them on every request:
var templates map[string]*template.Template
func loadTemplates() {
	if gin.IsDebugging() {
		// In development mode, load templates on every request
		return
	}
	
	// In production, load once
	templates = make(map[string]*template.Template)
	
	// Load various template combinations
	templates["home"] = template.Must(template.ParseFiles(
		"templates/layouts/base.tmpl",
		"templates/partials/header.tmpl",
		"templates/partials/footer.tmpl",
		"templates/pages/home.tmpl",
	))
	
	templates["about"] = template.Must(template.ParseFiles(
		"templates/layouts/base.tmpl",
		"templates/partials/header.tmpl",
		"templates/partials/footer.tmpl",
		"templates/pages/about.tmpl",
	))
}
func renderTemplate(c *gin.Context, name string, data gin.H) {
	if gin.IsDebugging() {
		// In development, use Gin's HTML renderer
		c.HTML(http.StatusOK, name+".tmpl", data)
		return
	}
	
	// In production, use cached templates
	tmpl, ok := templates[name]
	if !ok {
		c.String(http.StatusInternalServerError, "Template not found")
		return
	}
	
	c.Status(http.StatusOK)
	c.Header("Content-Type", "text/html")
	err := tmpl.Execute(c.Writer, data)
	if err != nil {
		c.Error(err)
	}
}
Adding Hot Reloading for Development
During development, it's helpful to have templates automatically reload when they change:
package main
import (
	"github.com/gin-contrib/multitemplate"
	"github.com/gin-gonic/gin"
	"net/http"
	"path/filepath"
)
// createRenderer creates a multitemplate renderer that reloads templates
func createRenderer() multitemplate.Renderer {
	r := multitemplate.NewRenderer()
	
	// Add base layouts
	layouts, err := filepath.Glob("templates/layouts/*.tmpl")
	if err != nil {
		panic(err.Error())
	}
	
	// Add pages with their layouts
	pages, err := filepath.Glob("templates/pages/*.tmpl")
	if err != nil {
		panic(err.Error())
	}
	
	// Generate our templates map
	for _, page := range pages {
		fileName := filepath.Base(page)
		
		// Create each page with all layouts
		templateFiles := append(layouts, page)
		
		// Add partials
		partials, err := filepath.Glob("templates/partials/*.tmpl")
		if err != nil {
			panic(err.Error())
		}
		templateFiles = append(templateFiles, partials...)
		
		r.AddFromFiles(fileName, templateFiles...)
	}
	
	return r
}
func main() {
	router := gin.Default()
	
	// Set the renderer
	router.HTMLRender = createRenderer()
	
	router.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"title": "Home Page",
		})
	})
	
	router.GET("/about", func(c *gin.Context) {
		c.HTML(http.StatusOK, "about.tmpl", gin.H{
			"title": "About Us",
		})
	})
	
	router.Run(":8080")
}
Summary
Working with multiple templates in Gin allows you to build complex, modular web applications with clean and maintainable code. Key points to remember:
- Use 
LoadHTMLGlob()to load templates from multiple directories - Organize templates into layouts, partials, and pages for better structure
 - Use template inheritance to avoid code duplication
 - Pass data from your Gin handlers to populate templates
 - Consider using custom template functions to enhance your templates
 - Implement caching for production environments
 - Use hot reloading during development
 
By mastering multiple templates in Gin, you can create sophisticated web interfaces while keeping your code organized and maintainable.
Additional Resources
- Gin Framework Official Documentation
 - Go Templates Documentation
 - Gin-Contrib Multitemplate - A middleware for Gin that allows multiple template engines
 
Exercises
- Create a portfolio website using Gin with multiple templates that includes a home page, projects page, and contact page.
 - Implement a blog system that uses layouts and partials to display blog posts and categories.
 - Add custom template functions that format dates, truncate text, and provide other common utilities.
 - Create a template caching system that reloads templates only in development mode.
 - Implement a theme system that allows users to switch between different layouts for your Gin application.
 
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!