Redesigning the Blog: From Risotto to Relearn
Why Change Anything?
The blog has been running on the Risotto theme since day one. I like Risotto β it’s clean and minimal β but as the post count crept past ninety, a single flat content/posts/ list stopped scaling. I wanted proper topic navigation, a homepage that actually surfaces recent writing, and a look closer to devopstoolkit.live, which happens to be built on hugo-theme-relearn.
So the goal became: migrate to Relearn, borrow its structure and visual system β dark, high-contrast, single accent color β but keep it clearly my site. My avatar, my Kavafis tagline, my links.
The two hard rules:
- Don’t lose any content.
- Don’t break any existing URL.
Installing the Theme (and a Shallow-Clone Gotcha)
Relearn goes in as a git submodule, pinned to a tagged release rather than main:
That timed out. Relearn’s repository carries a lot of history (the docs site, screenshots, every release), and a full git submodule add clone is heavy. The fix is a shallow clone at the tag, then register it as a submodule:
absorbgitdirs moves the nested .git into the parent’s .git/modules, so it behaves like a normal submodule from then on. If this sounds familiar, it’s the same family of pain as not initialising your submodules on clone.
A Custom Color Variant
Relearn’s theming is refreshingly sane: every variant is a plain CSS file at assets/css/theme-<name>.css that sets a bunch of custom properties. You can even @import a shipped variant and override just what you need.
I based mine on zen-dark β its modern single-topbar layout derives links, search and the active menu item from --PRIMARY-color, so changing one variable cascades everywhere. The result: Aegean Teal (#2dd4bf) on near-black (#0d1117), with dracula code blocks.
The dracula syntax theme isn’t shipped, but Hugo generates one for you:
The Gotcha That Cost Me Ten Minutes
My first build failed with:
I was using --CODE-theme. The culprit was a comment: I had written /* -> assets/css/chroma-dracula.css */ on the --CODE-theme line. Relearn scans the variant file for @import ... chroma-*.css, and its regex is non-greedy across newlines β so it matched my earlier @import "theme-zen-dark.css" and ran all the way down to the literal chroma-dracula.css sitting in that comment. Reworded the comment, build went green. Watch what you put in your CSS comments.
Restructuring 94 Posts Without Breaking URLs
The old layout was one flat section: content/posts/<slug>/index.md. The new one is five topic sections matching my focus areas:
Every post is a leaf bundle already, so this was a scripted git mv per post plus two edits to the front matter:
- Normalise the category (
devops,DevOps,DevopsβDevOps; fixwebex-twilio, which was mislabelledKubernetes, βVoIP). - Add an
aliasesentry pointing at the old URL.
That second point is the whole ballgame for not breaking links. Hugo turns each alias into a tiny redirect page:
Now /posts/webex-twilio/ 301s to /voip/webex-twilio/. Ninety-three redirect pages, generated automatically, zero broken bookmarks.
Each section also got an _index.md chapter page with a short intro and a newest-first list of its posts, via a small post-list shortcode.
A Homepage That’s a Feed
Relearn’s default homepage is a chapter list. I wanted the devopstoolkit rhythm: latest posts, each with a thumbnail, teaser and a Full article Β» link. That’s a layouts/index.html override that reuses the theme’s block structure (so the sidebar, topbar and search survive) and renders the twelve most recent posts across all sections:
Thumbnails by Convention
The feed resolves a thumbnail per post in this order:
- an explicit
thumbnail.*in the bundle, - otherwise the first image already in the post,
- otherwise a branded per-section placeholder.
That means the 43 posts that already ship a diagram or screenshot just work, and the rest fall back to a tidy near-black-and-teal card with the section’s emoji on it. Drop a thumbnail.png into any bundle later and it wins automatically β no config, no duplicated images.
Killing the External Search
The old setup queried an external Meilisearch instance (search.uclab.dev), fed by a build-time index.json and a CI step that pushed documents after every deploy. Relearn ships its own offline, in-browser search, so all of that came out:
- the Meilisearch indexing steps in both the GitHub and Forgejo pipelines,
- the
index.jsonoutput template, - the custom search page and the public search key baked into the HTML.
One fewer service to run, one fewer secret to rotate, and search now works with no network round-trip.
Keeping Every Commit Buildable
I worked in small steps β theme install, base config, color variant, content, homepage, cleanup β and built the site after each one. The final production build (the same hugo --minify the Docker image runs) lands at:
No missing-template warnings, no broken thumbnails, search index full of real titles.
Lessons Learned
- Shallow-clone big theme repos, then register the submodule by hand.
- Aliases are your safety net. Restructuring content is fine as long as every old path redirects.
- Relearn’s
@import+ CSS-variable approach makes a custom variant a ~50-line file, not a fork. - Comments are content too β a stray filename in a comment broke my build.
- Borrow another site’s system, not its identity. Same dark, single-accent skeleton; still my avatar, my tagline, my odyssey.
The map changed; the journey didn’t. Ithaka’s still the point.