Static Site Generator
A fast and simple static site generator written in Go.
About: Bringing technical concepts to life through clear writing. Go Software developer and Tech writer; creating, educating and inspiring. 👨💻Learning||Building🚀
Static site generators are powerful tools that simplify the creation of lightweight, fast, and scalable websites. Whether you're building blogs, documentation, or small business sites, they transform content written in Markdown into efficient, static HTML files.
In this guide, we’ll create a Static Site Generator (SSG) in Go, a programming language renowned for its performance, simplicity, and concurrency. We’ll build a CLI tool that takes Markdown files as input, processes them using a predefined HTML template, and outputs beautiful, static HTML pages.
A static site generator can serve several practical purposes:
I had a great fun time building this project :)
Before diving into the code, let’s outline the structure of the project:
static-site-generator/
├── cmd/
│ └── ssg/
│ └── main.go # Entry point
├── internal/
│ ├── generator/
│ │ └── html.go # HTML generation logic
│ ├── parser/
│ │ ├── frontmatter.go # YAML frontmatter parsing
│ │ └── markdown.go # Markdown processing
│ └── watcher/
│ └── watcher.go # File change detection
├── templates/
│ └── default.html # HTML template
├── content/ # Markdown files
└── output/
If you want to build from scratch, run this command to initialize a Go module for the project
go mod init
Key Features:
Convert Markdown to HTML 📄
YAML frontmatter for metadata parsing
HTML templates for customizable output
Real-time file change detection with a watcher 👀
Before starting, clone the repository to your local machine:
git clone https://github.com/Tabintel/static-site-generator.git
cd static-site-generator
A fast and simple static site generator written in Go.
This will give you all the starter files and project structure needed to build and run the SSG.
The Markdown parser handles converting .md
files into HTML content. It also enables extended features like automatic heading IDs.
internal/parser/markdown.go
package parser
import (
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/parser"
)
type MarkdownContent struct {
Content string
Title string
Date string
Tags []string
HTMLOutput string
}
func ParseMarkdown(content []byte) *MarkdownContent {
extensions := parser.CommonExtensions | parser.AutoHeadingIDs
parser := parser.NewWithExtensions(extensions)
html := markdown.ToHTML(content, parser, nil)
return &MarkdownContent{
Content: string(content),
HTMLOutput: string(html),
}
}
✨Converts Markdown content into HTML format with extended feature support.
The frontmatter parser extracts metadata like title, date, tags, and description from Markdown files.
internal/parser/frontmatter.go
package parser
import (
"bytes"
"gopkg.in/yaml.v2"
)
type Frontmatter struct {
Title string `yaml:"title"`
Date string `yaml:"date"`
Tags []string `yaml:"tags"`
Description string `yaml:"description"`
}
func ParseFrontmatter(content []byte) (*Frontmatter, []byte, error) {
parts := bytes.Split(content, []byte("---"))
if len(parts) < 3 {
return nil, content, nil
}
var meta Frontmatter
err := yaml.Unmarshal(parts[1], &meta)
if err != nil {
return nil, content, err
}
return &meta, bytes.Join(parts[2:], []byte("---")), nil
}
🎯 Extracts and returns metadata along with the content of the Markdown file.
The HTML generator uses Go’s html/template
package to create static HTML pages based on a template.
internal/generator/html.go
package generator
import (
"html/template"
"os"
"path/filepath"
)
type Generator struct {
TemplateDir string
OutputDir string
}
func NewGenerator(templateDir, outputDir string) *Generator {
return &Generator{
TemplateDir: templateDir,
OutputDir: outputDir,
}
}
func (g *Generator) Generate(data interface{}, outputFile string) error {
if err := os.MkdirAll(g.OutputDir, 0755); err != nil {
return err
}
tmpl, err := template.ParseFiles(filepath.Join(g.TemplateDir, "default.html"))
if err != nil {
return err
}
out, err := os.Create(filepath.Join(g.OutputDir, outputFile))
if err != nil {
return err
}
defer out.Close()
return tmpl.Execute(out, data)
}
🔧 Generates HTML files from templates and parsed Markdown content.
Our watcher monitors the content/
directory for changes and triggers rebuilds automatically.
This is built using https://github.com/fsnotify/fsnotify
internal/watcher/watcher.go
package watcher
import (
"fmt"
"github.com/fsnotify/fsnotify"
"log"
"os"
"path/filepath"
)
type ProcessFn func() error
func Watch(dir string, process ProcessFn) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write == fsnotify.Write {
fmt.Printf("Modified file: %s\n", event.Name)
if err := process(); err != nil {
log.Printf("Error processing: %v\n", err)
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Printf("Error: %v\n", err)
}
}
}()
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return watcher.Add(path)
}
return nil
})
if err != nil {
return err
}
<-done
return nil
}
🚰 Detects file changes and automates the regeneration of static files.
The entry point ties all components together and provides CLI options for customization.
cmd/ssg/main.go
package main
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"html/template"
"github.com/Tabintel/static-site-generator/internal/generator"
"github.com/Tabintel/static-site-generator/internal/parser"
"github.com/Tabintel/static-site-generator/internal/watcher"
)
func processFiles(contentDir string, gen *generator.Generator) error {
return filepath.Walk(contentDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if filepath.Ext(path) != ".md" {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return err
}
// Parse frontmatter and content
meta, content, err := parser.ParseFrontmatter(content)
if err != nil {
return err
}
// Parse markdown
parsed := parser.ParseMarkdown(content)
// Generate HTML
outputFile := filepath.Base(path[:len(path)-3]) + ".html"
return gen.Generate(map[string]interface{}{
"Title": meta.Title,
"Date": meta.Date,
"Tags": meta.Tags,
"Content": template.HTML(parsed.HTMLOutput),
"Description": meta.Description,
}, outputFile)
})
}
func main() {
// Define flags
contentDir := flag.String("content", "content", "Content directory path")
templateDir := flag.String("templates", "templates", "Templates directory path")
outputDir := flag.String("output", "output", "Output directory path")
watch := flag.Bool("watch", false, "Watch for file changes")
flag.Parse()
// Initialize generator
gen := generator.NewGenerator(*templateDir, *outputDir)
// Process files
err := processFiles(*contentDir, gen)
if err != nil {
log.Fatal(err)
}
if *watch {
fmt.Println("Watching for changes...")
err := watcher.Watch(*contentDir, func() error {
return processFiles(*contentDir, gen)
})
if err != nil {
log.Fatal(err)
}
}
}
Before you run the app, create a markdown file using .md
and save it in the content
directory
Then run the generator:
go run cmd/ssg/main.go
It converts the markdown file to an HTML file and saves it in the output
directory
As you can see, it adds formatting to make it visually appealing :)
Enable the watcher:
go run cmd/ssg/main.go --watch
And that's It!
This SSG converts markdown to clean HTML, watches for changes, and keeps your content organized. Drop a comment if you build something with it - I'd love to see what you create!
Found this helpful? You can buy me a coffee to support more Go tutorials! ☕
Happy coding! 🚀
A fast and simple static site generator written in Go.