sylvain durand

Gemini and Hugo

For a few years, I have been using Hugo, a static site generator, to produce these pages. At the same time very fast and corresponding perfectly to my needs, it is above all very modular.

I was therefore not surprised to see that it was quite easy to convert, with little effort, my site for the Gemini protocol. This was not done without some tricks. Let’s see how!

Declaring Gemini as an output format

Hugo can output content in multiple formats: most of them are already predefined, but it is also possible to create your own. This is what we are going to do for Gemini.

First, in the configuration file config.yml we will declare a new type text/gemini with the file suffix .gmi:

      - "gmi"

Once this is done, we declare a new output format, which uses this type, which is given the name GEMINI.

    name: GEMINI
    isPlainText: true
    isHTML: false
    mediaType: text/gemini
    protocol: "gemini://"
    permalinkable: true

Finally, it only remains to ask Hugo to generate pages for the different contents. For example, in my case:

  home: ["HTML", "RSS", "GEMINI"]
  page: ["HTML", "GEMINI"]

To be able to generate the files, it is now necessary to create layouts to see how to display them!

Index page

To start with the index, we can start with layout/index.gmi. For example, here is a simple text, followed by a list of posts:

## List of posts

{{ range .RegularPages }}
=> {{ .RelPermalink }} {{ .Title }}
{{- end }}

Here, I sort the articles in descending chronological order, grouping them by date. This gives the following code:

## Posts grouped by year

{{ range .RegularPages.GroupByDate "2006" }}
### {{ .Key }}
{{ range .Pages.ByDate.Reverse }}
=> {{ .RelPermalink }} {{ .Title }}
{{- end }}
{{ end }}


For posts, we can create a layout/_default/single.gmi. Basically, it would suffice to display the title and content:

# {{ .Title }}

{{ .RawContent }}


For images, I extract them with a simple regex and show them as a link:

{{- $content := .RawContent -}}
{{- $content = $content | replaceRE `\!\[(.+?)\]\((.+?)\)` "=> $2 Image: $1" }}
{{ $content }}

For the links, I decided to simply not use inline links on the site, but only put the links on a single paragraph. This allows me, as before, a very simple regex:

{{- range findRE `\[.+?\]\(.+?\)` $content }}
    {{- $content = $content | replaceRE `\[(.+?)\]\((.+?)\)(.+)` "$1$3\n\n=> $2 $1 " }}
{{- end }}

However, this is not a very satisfactory method when you have a site that has a lot of links online. A solution, proposed by the site Brain Baking, allows you to reference each link with a number ([1], [2]…) and then to put the links underneath, automatically, thanks to a clever code from Brainbaking.

If you want to add links for previous and next articles with:

{{ if .Next }}=> {{ .Next.RelPermalink }} ← Newer: {{ .Next.Title }}{{ end }}
{{ if .Prev -}}=> {{ .Prev.RelPermalink }} → Older: {{ .Prev.Title }}{{- end }}


To create RSS feeds, we can create a new output format, then define its layout.


We will do the same here! In config.yml, we define:

    baseName: "feed"
    mediaType: "application/rss+xml"
    isPlainText: false

  home: ["HTML", "GEMINI", ..., "GEMINI_RSS"]

Then, we create layouts/index.gemini_rss.xml with the following content:

<rss version="2.0" xmlns:atom="">
    <title>{{ .Site.Title }}</title>
    <description>{{ i18n "description" }}</description>
    <link>{{ (replace .Permalink "https://" "gemini://") | safeURL }}</link>
    <atom:link href="{{ .Permalink | safeURL }}feed.xml" rel="self" type="application/rss+xml" />
    {{- range .RegularPages }}
      <title>{{ .Title }}</title>
      <link>{{ (replace .Permalink "https://" "gemini://") | safeURL }}</link>
      <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
      <guid>{{ (replace .Permalink "https://" "gemini://") | safeURL }}</guid>
    {{ end }}

The RSS feed is now available on /feed.xml.


I use rsync to easily export my files to the server:


rsync -avz --no-perms --no-owner --no-group \
      --no-times --delete public/ vps:/var/gemini

rm -rf public

This last folder is then read by a gemini server, as explained in the previous article “Discovering the Gemini protocol”.