Hugo : le générateur statique qui construit ton site en millisecondes

Pourquoi jai quitté les frameworks JS et basculé sur un binaire Go

J’ai construit des sites avec Jekyll, avec Gatsby, avec Next.js. Et à chaque fois, le même problème : tu changes une virgule dans un article, tu relances le build, et tu vas te faire un café en attendant. 30 secondes. 1 minute. Parfois plus. Et ça, c’est juste pour le dev — en production avec quelques centaines de pages, c’est pire.

J’ai découvert Hugo y a quelques années. Et putain, la différence est violente. Un binaire Go unique. Zéro dépendance. Des builds en millisecondes. Et pas juste un petit générateur de blog — un vrai framework avec des templates, du multilingue natif, des taxonomies, un pipeline d’assets, le tout sans avoir besoin d’installer Node, Ruby, ou quoi que ce soit.

J’ai migré mon blog dessus. Voilà pourquoi.

C’est là mes définitions et si elles vous déplaisent, cassez-vous je suis pas là pour discuter lexique.

  • Hugo: Générateur de site statique écrit en Go. Un binaire, rien d’autre à installer.
  • Goldmark: Le processeur Markdown intégré à Hugo. Compatible CommonMark et GFM.
  • Shortcode: Un template appelé depuis le Markdown pour insérer du contenu complexe sans casser la mise en page.
  • Render Hook: Un template qui surcharge la conversion Markdown → HTML (liens, images, etc.).
  • Fast Render Mode: Mode dev qui ne reconstruit que les pages modifiées — littéralement 10ms.
  • Page Bundle: Organisation du contenu en dossiers avec leurs resources (images, fichiers).

Je vais pas cracher dans la soupe. Next.js et Gatsby, c’est bien. Mais pour un blog ou un site de documentation, c’est de l’artillerie lourde. Tu te tapes :

  • Node.js et ses node_modules qui pèsent 200 Mo
  • Webpack / Vite qui chauffe ton CPU
  • Des rebuilds de 30 secondes pour changer une typo
  • Une config à deux balles pour le multilingue
  • Et à la fin, tu sors des fichiers statiques — comme Hugo

La question que je me suis posée : pourquoi se faire chier avec tout ça alors qu’un binaire Go de 50 Mo fait le job en millisecondes, sans runtime, sans dépendances, sans config byzantine ?

Hugo est écrit en Go. C’est un binaire unique que tu télécharges et tu lances. Il va lire ton dossier content/, parser le Markdown, exécuter les templates, et écrire les fichiers HTML dans public/. C’est tout.

 0$ hugo new site mon-blog
 1$ tree mon-blog
 2mon-blog/
 3├── archetypes/
 4├── assets/
 5├── content/
 6├── data/
 7├── i18n/
 8├── layouts/
 9├── static/
10├── themes/
11└── hugo.toml

Cinq dossiers importants :

  • content/ : tes articles en Markdown. Hugo organise ça en arbre, avec des sections, des pages, des taxonomies.
  • layouts/ : tes templates HTML. C’est là que tu définis l’apparence de ton site.
  • assets/ : les resources qui passent par le pipeline (CSS Sass/PostCSS, JS TypeScript).
  • static/ : les fichiers copiés tels quels dans public/ (favicon, robots.txt).
  • hugo.toml : la configuration. Claire et lisible.

Quand tu lances hugo, le build se passe en cinq phases, tout en parallèle :

Parcours fichiers → Arbre de contenu → Assemblage → Rendu parallélisé → Écriture
  1. Parcours : Hugo scanne content/ et identifie les fichiers Markdown
  2. Arbre : chaque fichier devient une page. Le front matter est parsé. Le contenu est organisé en arbres (pages, resources, taxonomies)
  3. Assemblage : les pages sont liées (parent/enfant, suivant/précédent, traductions). Les menus sont construits
  4. Rendu : les templates sont exécutés en parallèle via un pool de workers (un par CPU)
  5. Écriture : les fichiers HTML/RSS/JSON sont écrits sur le disque

Le rendu parallélisé, c’est la clé. Pendant que la plupart des générateurs traitent les pages une par une, Hugo utilise tous tes CPUs. Résultat : 50 pages ou 5000, le temps de build scale à peine.

Hugo utilise les templates de Go — puissants, mais pas aussi verbeux que du Jinja ou du Liquid. Les templates vivent dans layouts/.

Le principe de base, c’est le baseof :

 0<!DOCTYPE html>
 1<html>
 2<head>
 3    <title>{{ block "title" . }}{{ .Site.Title }}{{ end }}</title>
 4</head>
 5<body>
 6    {{ partial "header.html" . }}
 7    <main>{{ block "main" . }}{{ end }}</main>
 8    {{ partial "footer.html" . }}
 9</body>
10</html>

Chaque page peut surcharger les blocs :

0{{ define "title" }}{{ .Title }} | {{ .Site.Title }}{{ end }}
1{{ define "main" }}
2<article>
3    <h1>{{ .Title }}</h1>
4    <p class="date">{{ .Date.Format "2006-01-02" }}</p>
5    {{ .Content }}
6</article>
7{{ end }}

Le lookup order est puissant : Hugo cherche le template le plus spécifique possible. Pour un article dans le dossier posts/ en français en format HTML, il va chercher dans cet ordre :

  1. layouts/posts/page.fr.html (chemin + kind + langue)
  2. layouts/posts/page.html (chemin + kind)
  3. layouts/page.html (kind global)
  4. layouts/single.html (fallback single)

Tu peux créer des templates spécifiques pour chaque section sans te prendre la tête.

Un shortcode, c’est un template appelé depuis le Markdown. J’utilise ça pour le code highlighté sur Thrashnochados :

{{< highlight Go "linenos=inline" >}}
func main() {
    fmt.Println("Hello")
}
{{< /highlight >}}

Ou pour les images :

{{< figure src="/images/banner.png" alt="Description" class="left" >}}

Les shortcodes permettent de garder un Markdown propre tout en ayant du contenu riche.

Parfois t’as besoin de modifier comment Hugo convertit le Markdown en HTML. Les render hooks font ça. Par exemple, pour ouvrir les liens externes dans un nouvel onglet :

0<a href="{{ .Destination }}"
1   {{ if strings.HasPrefix .Destination "http" }}target="_blank" rel="noopener"{{ end }}>
2    {{ .Text | safeHTML }}
3</a>

Tu mets ça dans layouts/_markup/render-link.html et c’est appliqué à tous les liens du site.

Pas de plugin. Pas de config à 100 lignes. Tu déclares les langues, tu créés des dossiers avec le code langue, et Hugo fait le reste :

[languages]
[languages.en]
contentDir = "content/en"
weight = 1
[languages.fr]
contentDir = "content/fr"
weight = 2

Les pages sont automatiquement liées entre les langues. Les templates peuvent adapter le contenu par langue. Les dates, monnaies, et tri suivent les conventions locales.

Tu veux des catégories et des tags ? Tu les déclares dans le front matter et Hugo génère tout :

[taxonomies]
tag = "tags"
category = "categories"

Hugo créé automatiquement les pages de listing, compte les pages associées, et peut trier par pertinence. Tout est accessible dans les templates via .Site.Taxonomies.

Les thèmes, c’est bien. Les modules Hugo, c’est mieux. Tu peux importer des packages qui apportent des templates, des assets, des données, des traductions. Et tu peux les surcharger localement sans les modifier. Le tout versionné et géré comme des dépendances Go.

$ hugo mod init mon-site
$ hugo mod get github.com/example/module

Le serveur de développement avec live reload est instantané. Tu modifies un fichier, et la page se recharge en moins de 10ms. Pas de HMR qui prend 3 secondes, pas de webpack qui recompile tout. Juste le fichier que t’as changé.

$ hugo server
Web Server is available at http://localhost:1313/
Press Ctrl+C to stop

Ajoute --navigateToChanged et le navigateur te suit automatiquement sur la page que t’es en train d’éditer. C’est un confort de malade.

Hugo gère le Sass/SCSS, PostCSS, la minification, et la concaténation sans outil externe. Tu mets tes fichiers dans assets/ et tu les appelles depuis les templates :

0{{ $css := resources.Get "css/main.scss" | toCSS | minify | fingerprint }}
1<link rel="stylesheet" href="{{ $css.RelPermalink }}" integrity="{{ $css.Data.Integrity }}">

Résultat : CSS minifié, fingerprinted, et chargé avec integrity hash. Sans webpack, sans parcel, sans rien.

 0# Installation (macOS)
 1$ brew install hugo
 2
 3# Création du site
 4$ hugo new site v4rgas
 5$ cd v4rgas
 6
 7# Ajout d'un thème
 8$ git init
 9$ git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke themes/ananke
10$ echo "theme = 'ananke'" >> hugo.toml
11
12# Premier article
13$ hugo new posts/mon-premier-article.md
14
15# Build et serveur dev
16$ hugo server -D  # -D inclut les brouillons

C’est tout. T’as un site fonctionnel en moins de 30 secondes. Et le build du site complet prendra moins de 100ms — même avec 500 articles.

J’ai rien contre JavaScript. Mais pour un blog, c’est de l’artillerie lourde. Tu te tapes :

  • npm install → 30 secondes
  • Build dev → 5-10 secondes
  • HMR → 1-3 secondes
  • Build production → 30-60 secondes
  • Dépendances → 200+ Mo de node_modules

Avec Hugo :

  • Un binaire de 50 Mo
  • Build en 100ms
  • Rien à installer à part le binaire
  • Zéro dépendance
  • Déploiement = copier public/ sur un serveur

Et au final, les deux produisent la même chose : des fichiers statiques. La question c’est : combien de temps t’es prêt à perdre pour le même résultat ?


Hugo c’est rapide, c’est propre, et ça fait le taf sans se prendre la tête. Si t’écris du contenu — blog, documentation, site vitrine — c’est le meilleur outil pour le job. Et une fois que t’as goûté aux rebuilds en millisecondes, tu peux plus revenir en arrière.

See ya space-cowboy!