skip to main content

Using Hugo to Launch a Gemini Capsule

gemini:// was launched!

published icon  |  category icon webdesign

tags icon gemini accessibility

As you can read in the “exploring the AlterNet” article, I’ve had my eye on Gemini for a few weeks now. Ever since discovering the new protocol thanks to a couple of weird Mastodon toots, I’ve been thinking about how to set up a “capsule” (they’re not called sites) for myself. I like the appeal of a text-focused, no-whizzbang protocol where the focus is on contents, not aesthetics, especially for blogs such as this one.

A few questions needed to be answered before switching to action modus and letting the static site generator Hugo do our dirty Gemini work for us.

How to host a Gemini capsule?

There are many pieces of Gemini software available to us, but they’re all quite new, as the protocol itself is from 2019. I was keen on trying out a simple Go server, but both go-gemini-server, shavit, and go-gemini required me to build it myself and contained very little documentation. Furthermore, some packages weren’t updated in more than a year… In the end, I decided to go with Agate, a simple Gemini server written in Rust that can serve static files. It has binaries for every platform, was updated six days ago, and its it extensive.

Agate even generates the needed TLS certificates if none are provided. This allowed me to quickly set up a localhost server using the command agate --content docs/gemini --addr --hostname localhost --lang en-US. Fun fact about the port number:

When Gemini is served over TCP/IP, servers should listen on port 1965 (the first manned Gemini mission, Gemini 3, flew in March ‘65).

Running locally before pushing to a server was important to me as I wanted to fiddle with the .gmi files first to see how they look like in my Gemini browser/client, Lagrange. Gotta double-check the ASCII art!

What to publish on Gemini?

This is very personal. There are a few options. People like Drew DevVault and Sylvain Durand mirror their HTTP(S) blog on Gemini, meaning all blog entries are consultable both over the web and over Gemini. Then there are more personal articles, published solely on Gemini to accompany the usually more technical HTTP blogs, such as gemini:// He claims to use it to whine like we did on MySpace yesteryear. I’ve also seen hybrids popping up: articles that are ported, but some exclusive content is also available through Gemini. I like that. My method at least makes this possible.

I wanted to blog in Dutch, my mother language, for a while now, and I’ve tried it a few years back on Brain Baking. It didn’t work out. The entries were misplaced somehow and I wasn’t satisfied, even though I did not expect to actually have readers. I hoped to use a new domain,, for a Dutch Gemini capsule to do some personal whining. That sounded like a good plan.

The plan fell through. Instead, I decided to mirror Brain Baking. Why?

  • I already whine in Dutch in my personal diaries using a fountain pen. I do not want to give that up.
  • I already have a (nice?) blog, and I’d like to expand the Gemini space-i-verse by adding my existing articles to it. I already write in Markdown, so a conversion would be not too difficult.
  • I don’t think I can keep up with posting on yet another blog, since I also occasionally write about retro games on

How to publish on Gemini?

Right. Porting articles turns out to be ridiculously easy with the help of my good old friend, Hugo. Sylvain’s method for declaring Gemini as a custom Hugo output format turned out to work flawlessly. All credits go to him. However, I did make a few significant changes to the link replacement system. First, something important to consider: I do not get rid of special emphasis symbols such as underscores or stars, that are Markdown-specific. I still think they add something when reading plain text and they’re the next best thing to have without any markup at all. So I removed those regex-es.

Gemini pages cannot have inline links, so I had to strip out Markdown-style []() links and place them on a separate paragraph using => link title. A simple find-and-replace, like in Sylvain’s method, is quite ugly if you use inline links extensively like I do. It breaks up the text and the result is a difficult to read Gemlog (that’s a Gemini blog!). In my approach, I collect all links, replace them with a reference number like in academic papers ([1]), and add a section called “References” on the bottom of the article to list them all. This is what it looks like:

My Gemini AlterNet article in Lagrange.

I’m quite pleased with the result, although the code itself is far from pretty, as Gemini is very newline-sensitive, and I had to jam a bunch of Hugo-specific regex functions together. Source code available at GitHub: index.gmi source, single.gmi source. Next to the link change, I also replaced all - and 1. (number) lines, that are enumerators in Markdown, with * ones, which is the only supported enumerator in Gemfiles.

I tried to design the index and single layout files as similar as possible to their html variants, while focusing in simplicity. Related articles are also visible at the end of an article, and the index file simply contains a short bio followed by an overview of all posts, groupbed by year and month, just like in my html /post overview. After defining [outputFormats.GEMINI] in my Hugo config.toml, all that was left is to use rsync to copy over the gemini subfolder to an appropriate location that gets picked up by Agate. Job done!

Well, not entirely. My Markdown files are littered with surprisingly Hugo-specific junk:

  • Shortcodes, such as YouTube, embedded video or audio.
  • Four hashes - h4 - which isn’t supported by the Gemini protocol.
  • <span/> tags in my quotes that help with HTML markup.
  • Links to aliases that are redirects, which don’t work for the Gemini output format.

Also, after trying out a second Gemini client, the terminal-friendly Amfora, I noticed the reference numbers do not align with Amfora’s shortcut keys that allow you to quickly navigate to a link. Reference 1 would match to key 2. Why? Because an image is also converted to a link (=> url), wich is placed in-between text, while the actual references are at the bottom. Hence, pressing number one would let us download the image - except Amfora can’t handle that (yet). I solved this by starting at a specific index, based on the number of times the arrow notation is present in the .gmi file, before processing inline links. These are all things to take into account when writing future posts.

Now, the the most important question, “why publish on Gemini” could be answered with “because it’s easy!”. I’m not yet sure if that answer is very satisfactory, but at least Brain Baking got launched into Space today 🚀! All that is left is to submit it to the GUS Gemini Universal Search engine…

I'm Wouter Groeneveld, a level 35 Brain Baker, and I love the smell of freshly baked thoughts (and bread) in the morning. I sometimes convince others to bake their brain (and bread) too.

If you found this article amusing and/or helpful, you can buy me a coffee - although I'm more of a tea fan myself. I also like to hear your feedback via Mastodon or e-mail. Thanks!

A bunch of ideas for my website that might never get implemented.Wanna know more about the website? Check out the meta page!Change the font-size to 17px (more?). be careful with different device sizes. Mobile keep 16. Test.Reading sectionCreate backe...

 | by