Replacing Hugo with Go
November 16, 2025
I built a static site generator that fits me better than Hugo ever did.
Sunday Morning, Tired of Hugo
I've been using Hugo for years. It works fine. But every time I wanted to change something I had to relearn Hugo's template and config and theme system. Again.
So one Sunday morning, I started an opencode session with kimi k2 and started prompting. By lunch, I had something that could parse my markdown files and generate valid HTML. By dinner, it had hot reload and RSS feeds.
And that's not even close to "focused work" - I was in charge of my 2 year old for most of that time, so I was making purely drive-by review and followup-prompts. AI Tools have given me speed and fun this year and I'm excited to see where they go next.
What I Actually Built
The whole thing is 845 lines of Go that actually do something:
- main.go: 19 lines - CLI entry point
- build/build.go: 737 lines - Site generation logic
- server/server.go: 89 lines - Development server with hot reload
Plus 16 lines of documentation in doc.go because that's what you do in Go.
Total: 861 lines, but the core functionality is really just the 845 lines in main.go, build.go, and server.go.
It does exactly what I need:
- Reads markdown with YAML frontmatter
- Converts to HTML with Goldmark
- Applies simple Go templates
- Generates RSS feed
- Copies static files (CSS, images, etc.)
- Serves with hot reload during development - actively watches content/ and rebuilds when you edit files
- Handles both posts and static pages
- Supports Mermaid diagrams and syntax highlighting via CDN
That's it. No themes, no plugins, no configuration files beyond the Makefile.
Why It Works Better
I understand every line. When something breaks, I know exactly where to look. When I want to change the RSS format or add syntax highlighting, I just... do it. No documentation diving, no theme inheritance to untangle.
It fits my content exactly. My generator knows my posts have titles, dates, and descriptions. It knows I want clean URLs. It knows I like my particular typography choices. These aren't configurable because they don't need to be—they're just right for me.
The feedback loop is instant. Save a file, see the change. The server actively watches my content directories and rebuilds automatically. No build pipelines, no dependency management, no complex tooling.
The Code
// main.go
func main() {
if len(os.Args) > 1 && os.Args[1] == "serve" {
server.Serve()
} else {
build.BuildSite()
}
}
The build package walks content/, parses markdown, applies templates, writes to public/. The server package watches files and rebuilds. Everything else is just details.
What I Learned
Simple tools are joyful tools. My generator does exactly what I need and nothing more. Every feature exists because I actually use it.
Building for yourself is freeing. No need to handle edge cases I'll never encounter. No need to make things configurable that I'll never reconfigure. Just solve my actual problems.
Sometimes you should just build it. I spent more time reading Hugo documentation over the years than I spent building my replacement. The math works out.
The Trade-off
This only works because I don't need:
- Multi-language sites
- Complex taxonomies
- Plugin ecosystems
- Theme inheritance
- Asset pipelines
If you need those things, Hugo is probably perfect. I just didn't.
The Point
I built a tool that fits me exactly, and it took less than a day with AI assistance. Sometimes the best solution isn't the most powerful one—it's the one that gets out of your way and lets you do your work.
For now the code is in my private blog repo (with so many partial drafts). It's not impressive, but it's mine, and that's exactly what I needed.