A peek under the hood

Joachim Robert -
Cet article est aussi disponible en français : Un coup d’œil sous le capot

Illustration of a website being built

« Hey Joachim, you don’t have a lot to do? Good news, I’ve got a project for you. »

This is how Olivier, my new boss manager team lead, told me about his idea a few days after I started working at Gandi. The tech team wants to talk about tech on the web, so he asked me to build a blog.

How would you make a blog in 2023? When I started blogging, you got a Wordpress hosting solution… but we chose another way: in order to limit our needs in infrastructure and have great performance, we wanted to try static website generators. And as very few people in the team know Go, we chose a generator in Go. Quite logical.

Enter Hugo

A static site generator has one mission: using contents written in text files, it will generate HTML files that we’ll then place on a web server. A great generator will also allow for taxonomies (like the tags at the bottom 👇), flexible theming capacities and quick onboarding.

Having never handled Hugo, this latter quality impressed me. In a very short time and some not-so-short command lines, it was ready… or I mean, I had a website on my computer with a default theme and a first post in fake latin. Still far away from a complete website.

So. What now?

Multi-lang structure

One of the main demands for this project was to be able to post contents in English and in French. In order to do that, you have to adjust Hugo’s settings a little bit.

First, we chose a double path tree, using /en/ and /fr/ to differentiate languages. The settings are quite explicit:

# config.toml

    title = "Gandi Blog"
    languageName = "English"
    weight = 1
    contentDir = "content/en"

    title = "Blog Gandi"
    languageName = "français"
    weight = 2
    contentDir = "content/fr"

The English version will be served by default, but Hugo needs to be told that the default language should be served from its own directory, not from the root:

# config.toml

defaultContentLanguageInSubdir = true

So this is it for the content architecture. When it comes to the language displayed, Hugo does it all automagically, you just have to use i18n tags to translate the bits of interface to the right language. Translation files use a .toml fromat, whose main advantage is that it’s human-readable. There seem to be limited ways to integrate the translation files in the kinds of translation management systems we use on bigger projects, but the scope of a blog is such that I don’t think we’ll even reach fifty different translated terms.

When two posts in different language trees have the same ID, each is considered as a translation of the other. So I decided to display a link to the translation at the start of the post:

<!-- layouts/partials/page-translated-list.html -->

{{ if .IsTranslated }}
  {{ range .Translations }}
  <span lang="{{ .Lang }}">
    {{ i18n "translations" .Language.LanguageName }}
    <a href="{{ .Permalink }}">{{ .Title }}</a>
  {{ end }}
{{ end }}

How to search a static website?

There’s always an important question when making a static website: how will you handle your internal search? You don’t have a dynamic back-end that can make queries against all the content in a database. You don’t even have a database. So it’s a very good question.

The search system must be client-side. So we used the Pagefind utility. Whenever Hugo compiles the contents to HTML pages, we ask Pagefind to crawl over said pages to index them. Then, the utility will generate a Javascript / WASM script and index files, that will be used by the website user.

When the user uses the search function, the script will handle everything and download the indexes in order to search through them as needed.

Read us in styles

There’s another new thing that we tried out when developing this blog, and it’s related to how we handle styles. For some time now, Pascal (the front-end developer responsible for our Design System) works on a system of design tokens to ease the discussions and facilitate the handover from designer to developer.

The developers at Gandi work in separate teams, for each of the different products and services we offer. One for the domains, one for the DNS, one for the hosting, the websites, etc. These teams need to use the same design elements, to maintain uniformity in the look and feel of the apps and websites. That’s where design tokens are useful: they are concrete bits of information, in the form of basic values shared between all the teams. These tokens are already in our design setup: designers have a limited set of colors, typefaces, spacing values to choose from ; these values are also starting to be tested in our apps and websites. It allows the developers in our various teams to have only one source of truth. When we start using this system, if there’s a change to the primary colour of the Gandi brand, we’ll need to change its value in one file only, and this information will be passed on to the applications and websites. Therefore, the risk of errors will be reduced and the deployment of changes in the brand identity will be simpler.

In fact, the website that you’re reading already uses an alpha version of these tokens. They cover colors, typography, font sizes, line heights, dimensions, spacings, shadows… As I write this, there are about 200 values defined.

These values have been passed on to me by the central authority (erm, by Pascal), as CSS Custom Properties. But our front-end projects in React will be able to get them as JavaScript or Sass variables. That’s the strength of this system: whatever the concept or the framework that powers a project, you can get these tokens and use them natively.

Contribution and deployment

Everything above is just the foundations. The most important part of a blog is the content (and the fact that it’s online and visible).

Allowing the team to write articles…

We use a flat file content management structure: all the information is contained in text files that belong to a directory tree. No database, no complications.

I’m writing right now in a Markdown file. I could also write in many alternative formats—AsciiDoc, reStructuredText, Pandoc or HTML—and I don’t doubt that I’ll have one or two teammates who’ll contribute in Org Mode: a format used with Emacs. Hugo will then convert all these types of content to pages, generate the index pages, the archives, etc.

When using text files, one of the best ways to allow anyone in the team to contribute is to put the files in a code repository. With Gitlab, our software forge of choice, we’ll even be able to contribute from the web, thanks to the integrated development interface.

These technical aspects can deter people who would want to write something, so I documented the steps needed to submit an article. Without it the opportuniny to contribute is hindered by the difficulty of the task.

Once the article is on the Git repository, the rest of the team can read it, ask for modifications if necessary, and approve them.

…without any technical requirement when publishing it

Automation is a beautiful thing. For a few years now, software forges enable us to start actions whenever changes are made to the code. These actions can be very complex, and bear the sweet name of CI/CD (Continuous Integration/Continuous Deployment). They allow us to test the code, enforce our team’s coding style rules, test each feature, and then test them all together in a staging environment… and then if they pass, the actions will deploy the code on production servers.

Whenever a new article is in the repository, the CI/CD actions will:

  1. create a virtual machine, install Hugo and all our dependencies necessary to…
  2. …compile the contents and make a website as described above,
  3. push the website to a server.

And so, a couple of minutes after the team’s review and validation of the article, the website is online and visible by the public in all its HTML glory (or RSS if you’re into that type of thing).


As a first public project since I started working at Gandi, I’m happy to have been able to test so many new things—new to me, and new to the team… one of the reasons to pick this carreer was the frequent opportunities to learn new concepts and new practices. The other reason is that it allows me to make useful and practical projects that respect the people that’ll use them. I hope I’ll have fulfilled these missions for my new colleagues and for you, the reader.