Skip to main content
  1. Posts/

Building With Hugo

·1204 words·6 mins
Photograph By Nick Morrison
Blog Hugo Web Development
Table of Contents
Building This Website - This article is part of a series.
Part 2: This Article

The Other Half
#

In my last post about this site , I covered how it’s hosted on Cloudflare. But I skipped over something kind of important — how it’s actually built. I promised I’d come back to that, so here we are (only slightly late). This is the Hugo post.

For the uninitiated, Hugo is a static site generator. You write content in markdown, Hugo turns it into HTML, and you throw the HTML onto a server. No database. No backend. No React hydration bundles. Just files. And it does this absurdly fast — we’re talking milliseconds per page. The dev server reloads before my brain finishes processing the save.

Why Not Just Use Next.js?
#

Fair question. I use TypeScript professionally, and Next.js has a static export mode. But here’s the thing — this site is meant to showcase my thoughts and experiences, not my frontend skills. I didn’t need TanStack, client-side routing, or backend wiring for what is essentially a collection of markdown files. No node_modules, no dependency updates, no build tool configuration. Hugo is a single binary. You download it, you run it, and it works. There’s something deeply satisfying about that.

Plus, Hugo is written in Go, and Go is a language I want to explore more in the future. So working with Go templates — even if it’s a small exposure — is a nice excuse to get comfortable with the ecosystem.

The tradeoff is Go templates. If you’ve never written Go templates before, prepare to question your life choices for the first day or two. The syntax reads like someone took HTML and sprinkled in curly braces with dots and pipes. But once it clicks, it’s actually fine. You spend a lot less time in templates than you think — most of the work is in markdown and configuration.

Here’s a quick taste for the curious:

{{ if .Params.photographer }}
  <span>Photo by {{ .Params.photographer }}</span>
{{ end }}

{{ range .Pages }}
  <h2>{{ .Title }}</h2>
  <p>{{ .Summary }}</p>
{{ end }}

Yeah, it’s not winning any beauty contests. But it gets the job done.

The Anatomy of This Site
#

Hugo projects follow a specific structure, and once you understand it, everything makes sense:

homepage/
├── config/_default/    # Site settings (split into multiple files)
├── content/posts/      # Blog posts (markdown)
├── assets/css/         # Custom CSS (processed by Hugo)
├── layouts/            # Template overrides
├── static/docs/        # Static files (resume PDF)
└── themes/blowfish/    # The theme (git submodule)

The key insight is that Hugo has a layered override system. The theme provides base templates, and anything you put in your site’s layouts/ folder takes priority. So you never touch the theme’s files directly — you copy the file you want to change into your own layouts/ directory and modify it there. This means you can update the theme without losing your customizations. It’s actually a really elegant system.

Page Bundles (or: Keep Your Stuff Together)
#

Each blog post lives in its own folder:

content/posts/building-with-hugo/
├── index.md          # This post
├── featured.jpg      # The header image
└── screenshot.png    # Any other images used in the post

Hugo calls these “page bundles,” and they’re great. Instead of dumping all your images into a static/images/ folder and losing track of which image belongs to which post, everything stays together. Delete a post? Delete the folder. Done. No orphaned images lingering around for eternity.

Blowfish — The Theme
#

I went with Blowfish because it checked all my boxes:

  • Dark mode that actually looks good (with auto-switching based on OS preference)
  • Built-in search (no plugins or third-party services)
  • Table of contents, reading time, word count — all toggleable
  • Code syntax highlighting with a copy button
  • Related posts based on categories and tags
  • Clean, not bloated

The configuration is split across a few TOML files, which I actually prefer over one giant config file:

# params.toml — the fun stuff
colorScheme = "ocean"
defaultAppearance = "dark"

[homepage]
  layout = "background"
  homepageImage = "img/background-home.jpg"
  showRecent = true
  recentLimit = 3
  cardView = true

[article]
  showHero = true
  heroStyle = "background"
  showTableOfContents = true
  showReadingTime = true

Most of the customization is just flipping booleans. Want word count? showWordCount = true. Want related posts? showRelatedContent = true. It’s the kind of developer experience that makes you wonder why everything isn’t this simple.

The Custom Bits
#

Out of the box, Blowfish is solid. But I wanted to make it feel a little more like mine. Here’s what I changed:

Fonts — I swapped the defaults for Inter (body text) and Archivo (headings). This goes in extend-head.html, which Hugo injects into the <head> of every page. No theme files touched.

Animations — This is where I might have gone a little overboard. I added scroll-triggered reveal animations using the Intersection Observer API, card hover effects, a reading progress bar, and even CSS View Transitions for smooth page-to-page navigation. All of it respects prefers-reduced-motion because accessibility matters (a theme that came up in a recent post ).

A download button shortcode — Hugo lets you create custom shortcodes, which are basically reusable content snippets. I made one for download buttons so I could embed a resume download link without writing raw HTML every time:

{{< download-button href="/docs/resume.pdf" download="resume.pdf" >}}
  Download Resume
{{< /download-button >}}

The actual implementation is just a styled <a> tag in a template file. Simple, but it keeps the markdown clean.

The Development Loop
#

This is probably my favorite part. The development workflow is:

hugo server --disableFastRender --noHTTPCache --buildDrafts

That’s it. Save a markdown file, and the browser refreshes almost instantly. There’s no build step to wait for, no hot module replacement to break, no “rebuilding 47 modules…” messages. Just save and see. After working with Next.js and Webpack in my day job, this feels like cheating.

For production, the build is equally simple:

hugo --gc --minify

The --gc flag cleans up unused cache files, and --minify compresses everything. The entire site builds in under a second. The output goes to a public/ folder, which is what gets deployed to Cloudflare via the build script I covered before .

Would I Recommend Hugo?
#

If you’re a developer who wants a blog and you’re comfortable with the terminal — yes. Especially if your content is primarily text and images. Hugo does that better than anything else I’ve tried, and the build speed means you’re never waiting around.

If you want interactive components, embedded React widgets, or a complex web app that also has a blog — look at Astro instead. It gives you component islands (use React/Vue/Svelte only where you need interactivity) while keeping the rest static. Hugo doesn’t do that.

And if you just want a blog with zero setup, honestly, just use GitHub Pages with Jekyll. It works out of the box, and you’ll be writing content in minutes.

But for me, Hugo hit the sweet spot. Fast builds, markdown-first, no JavaScript dependencies, and a theme system that gets out of your way. The Go template learning curve was real, but it was a weekend, not a month. And now I have a site that builds in under a second and costs me nothing to host. That’s hard to beat.

Aaron Yong
Author
Aaron Yong
Building things for the web. Writing about development, Linux, cloud, and everything in between.
Building This Website - This article is part of a series.
Part 2: This Article

Related

Deploying a website with Cloudflare & Hugo
·834 words·4 mins
Photograph By Alexey Demidov
Blog Hugo Deployment
Static website deployment with Cloudflare and Hugo
Things I Changed My Mind On
·1353 words·7 mins
Photograph By ThisisEngineering - Unsplash
Blog Software Engineering
Opinions that didn’t survive contact with production
Living in the Terminal
·1145 words·6 mins
Photograph By Oleksandr Chumak
Blog Terminal Productivity
How TUIs replaced my GUI tools and accidentally made my workflow more accessible