Hugo is the world's fastest static site generator. Here's why it's built in Go and how to test your Hugo themes.

Hugo: The Static Site Generator That Powers This Blog


This blog runs on Hugo. So do thousands of others. It’s fast, flexible, and entirely written in Go.

Hugo is a static-site-generator that turns Markdown files into complete websites. No database. No server-side code. Just HTML, CSS, and JavaScript. The result? Sites that load fast and scale easily.

But today I want to talk about something specific: testing Hugo themes and content. If you’re building a Hugo site, you’ll want to make sure your templates work correctly.

Why Hugo Chose Go

Hugo’s speed comes from Go’s performance characteristics. Building a site with thousands of pages takes seconds, not minutes. The single binary distribution means no runtime dependencies. Drop the executable on any machine and it works.

The Go standard library provides everything Hugo needs. Template rendering uses html/template. File watching uses fsnotify. The HTTP server for development comes from net/http. This keeps external dependencies minimal.

If you’re interested in how Go handles templates, the text/template package documentation is worth reading.

Testing Hugo Templates

Hugo templates use Go’s template syntax. Testing them requires understanding how the template engine works.

Here’s a simple example of testing a template function:

package main

import (
	"bytes"
	"html/template"
	"testing"
)

func TestTitleTemplate(t *testing.T) {
	tmpl := `<h1>{{ .Title }}</h1>`
	
	parsed, err := template.New("title").Parse(tmpl)
	if err != nil {
		t.Fatalf("failed to parse template: %v", err)
	}
	
	data := struct {
		Title string
	}{
		Title: "Hello, Hugo!",
	}
	
	var buf bytes.Buffer
	if err := parsed.Execute(&buf, data); err != nil {
		t.Fatalf("failed to execute template: %v", err)
	}
	
	expected := "<h1>Hello, Hugo!</h1>"
	if buf.String() != expected {
		t.Errorf("got %q, want %q", buf.String(), expected)
	}
}

This pattern works for unit testing individual template snippets. For more complex scenarios, you’ll want integration tests.

Integration Testing with Hugo’s Test Server

Hugo includes a built-in server for development. You can use it in tests to verify your site builds correctly:

package main

import (
	"net/http"
	"os/exec"
	"testing"
	"time"
)

func TestHugoSiteBuild(t *testing.T) {
	// Build the site
	cmd := exec.Command("hugo", "--destination", "public_test")
	if err := cmd.Run(); err != nil {
		t.Fatalf("hugo build failed: %v", err)
	}
	
	// Start the server
	server := exec.Command("hugo", "server", "--port", "1314", "--disableLiveReload")
	if err := server.Start(); err != nil {
		t.Fatalf("failed to start server: %v", err)
	}
	defer server.Process.Kill()
	
	// Wait for server to start
	time.Sleep(2 * time.Second)
	
	// Test the homepage
	resp, err := http.Get("http://localhost:1314/")
	if err != nil {
		t.Fatalf("failed to fetch homepage: %v", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		t.Errorf("homepage returned %d, want %d", resp.StatusCode, http.StatusOK)
	}
}

This approach catches broken links, missing templates, and configuration errors. Similar to how you’d test any Go HTTP service, as covered in testing HTTP handlers.

Testing Content with Regular Expressions

Sometimes you need to verify specific content appears on pages. Go’s regex package handles this well:

package main

import (
	"io"
	"net/http"
	"regexp"
	"testing"
)

func TestMetaDescription(t *testing.T) {
	resp, err := http.Get("http://localhost:1313/blog/my-post/")
	if err != nil {
		t.Fatalf("failed to fetch page: %v", err)
	}
	defer resp.Body.Close()
	
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("failed to read body: %v", err)
	}
	
	// Check for meta description
	pattern := `<meta name="description" content=".+">`
	matched, err := regexp.Match(pattern, body)
	if err != nil {
		t.Fatalf("regex error: %v", err)
	}
	
	if !matched {
		t.Error("meta description not found")
	}
}

If regex feels unfamiliar, check out the Advent of Code day 3 post where I covered Go’s regex patterns in detail.

Hugo as a Documentation Tool

Hugo works great for documentation sites. Many open source projects use it. The content-management-system approach fits technical writing well. Markdown files live in version control. Multiple authors can contribute through pull requests.

For a blog-engine, Hugo hits the sweet spot. It’s a cms without the complexity. Your content stays in plain text files. No database migrations. No security patches for PHP.

Getting Started

Install Hugo with a single command:

go install github.com/gohugoio/hugo@latest

Create a new site:

hugo new site my-blog
cd my-blog
hugo server

That’s it. You have a local development server running.

Wrapping Up

Hugo shows what Go does well: fast compilation, simple deployment, and reliable performance. Testing Hugo sites uses the same patterns as any Go application. Templates, HTTP requests, and regex all work the same way.

If you’re building a static site and want something fast, Hugo is worth trying. The official documentation covers everything from basic setup to advanced templating.