[🛠] Réduire le temps de build du blog multilingue de 21 minutes à environ 1 minute
✨ Résumé de GPT-5.5  
Récit du suivi, avec le profil Jekyll, d’un build qui dépassait 20 minutes après l’extension multilingue, puis de la suppression des rendus répétés et des scans de tout le site jusqu’à descendre à 1 minute 50 secondes.
Dans l’article précédent sur l’introduction multilingue, j’ai ajouté au blog une structure d’exploitation multilingue.
Au début, j’en étais assez fier.
ko, en, ja, zh-Hans, es, pt-BR, fr, id.
Il y avait des articles, des menus, du hreflang, et les vues étaient partagées. Vu de l’extérieur, c’était devenu un blog multilingue assez plausible.
Mais un autre problème est apparu tout de suite.
Le build prenait beaucoup trop de temps.
Le premier production build ressemblait Ă ceci.
done in 1311.791 seconds
21 minutes et 52 secondes.
Ce n’est pas un chiffre qu’un simple blog devrait produire. Si corriger un seul article fait dépasser 20 minutes de build, je finirai par passer plus de temps à attendre les builds qu’à écrire.
Au début, on pouvait être tenté de penser simplement : “Comme il y a maintenant huit langues, forcément c’est devenu plus lent.”
Mais c’était une conclusion beaucoup trop rapide.
Le nombre de pages avait bien augmenté. Mais atteindre les 20 minutes signifiait qu’il y avait autre chose. Alors cette fois, au lieu de corriger au feeling, j’ai lancé jekyll build --profile.
Premier coupable : le JSON du calendrier était injecté dans toutes les pages
J’ai d’abord regardé la taille de _site.
_site: 1.2G
HTML total: environ 840MB
840 Mo de HTML pour un blog statique.
Ça n’avait aucun sens.
J’ai ouvert un article représentatif, et la raison est apparue immédiatement. Le calendrier de la sidebar injectait, en inline, la liste JSON complète des articles de la langue concernée dans chaque page d’article.
<script type="application/json" data-calendar-posts>
[
...
]
</script>
Et ce n’était pas tout.
Pour fabriquer la liste de fallback du calendrier, Liquid parcourait de nouveau toute la liste des articles pour chaque date. Même les articles qui ne correspondaient pas à la condition produisaient une énorme quantité d’espaces Liquid. L’interface visible était petite, mais à l’intérieur du HTML se cachaient de gigantesques blocs d’espaces et de listes répétées.
Ce n’était pas un problème de fonctionnalité. C’était un problème de structure.
Le calendrier n’avait pas besoin que le serveur fabrique un HTML complet sur chaque page. Le JS savait déjà dessiner le calendrier dynamiquement. Dans ce cas, le serveur devait seulement fournir le squelette du mois courant et le chemin vers le fichier de données.
J’ai donc sorti les données du calendrier dans des fichiers JSON séparés par langue.
/assets/data/calendar-posts-ko.json
/assets/data/calendar-posts-en.json
/assets/data/calendar-posts-ja.json
/assets/data/calendar-posts-zh-Hans.json
/assets/data/calendar-posts-es.json
/assets/data/calendar-posts-pt-BR.json
/assets/data/calendar-posts-fr.json
/assets/data/calendar-posts-id.json
Et il ne reste plus que ceci dans la page.
data-calendar-posts-src="/assets/data/calendar-posts-en.json"
Le résultat a baissé immédiatement.
1311.791 secondes -> 745.273 secondes
Presque la moitié avait disparu, mais c’était encore long.
C’est là que j’ai compris.
Le calendrier était un gros coupable, mais ce n’était pas le seul.
Deuxième coupable : les statistiques du menu étaient calculées 80 000 fois
Le profil suivant était encore plus flagrant.
_includes/sidebar-nav-stats.html 81120 calls 173.084s
_includes/masthead.html 2704 calls 319.860s
_includes/seo.html 2704 calls 119.985s
sitemap.xml 1 call 117.495s
Le plus drôle, c’était sidebar-nav-stats.html.
Cet include est un petit fragment qui ajoute le nombre d’articles et l’heure du dernier article à côté de chaque catégorie de la sidebar.
Par exemple, quelque chose comme ceci.
Daily Review (310) 1 days ago
Devlog (24) 2 days ago
Mais chaque fois que ce petit fragment était appelé, il triait toute la liste des articles, puis la filtrait de nouveau.
Pour chaque élément de menu de chaque langue.
Sur chaque page.
Et encore une fois dans la sidebar desktop et dans le menu mobile.
Résultat : 81 120 appels.
Cette valeur n’a pas besoin d’être recalculée pour chaque page. Si la langue et l’URL du menu sont les mêmes, le résultat est le même. Je l’ai donc remplacée par include_cached de jekyll-include-cache.
{% include_cached sidebar-nav-stats.html url=child.url lang=current_lang %}
Le nombre d’appels est alors devenu ceci.
81120 calls -> 210 calls
173 secondes -> 0.4 seconde
À ce niveau-là , ce n’était presque plus de l’optimisation. C’était plutôt un bug attrapé en plein vol.
Troisième coupable : le site scannait tout pour fabriquer les liens de langue
En ajoutant le changement de langue, j’avais mis dans masthead, seo et sitemap une logique du genre :
Cette URL existe-t-elle vraiment dans cette langue ?
L’intention était juste.
Il ne faut pas mettre une URL de traduction inexistante dans hreflang ou dans le menu de changement de langue. Au début, je vérifiais donc l’existence des URL en parcourant toutes les pages et tous les documents de collection.
Le problème, c’est que cela se répétait sur chaque page.
2704 pages * site.pages scan * translated collections scan
Plus un site multilingue grandit, plus cette structure empire.
J’ai donc changé de méthode.
Ce blog possède déjà une règle pour les URL traduites.
/some/post/
/en/some/post/
/ja/some/post/
...
Et les exceptions peuvent être gérées séparément comme des données.
Cette fois, j’ai placé dans _data/i18n_pending.yml l’article de retour de session dont la traduction était encore en attente.
entries:
- source_url: /devlog/github-pages-blog/github-pages-blog-english-version-lessons/
locales:
- en
- ja
- zh-Hans
- es
- pt-BR
- fr
- id
Ainsi, les articles ordinaires sont reliés par la règle de préfixe, et les articles en attente retombent vers la page d’accueil de l’autre langue. On ne scanne plus tout le site.
L’effet a été important.
masthead: 319.860 secondes -> 9.882 secondes
seo: 119.985 secondes -> 7.181 secondes
sitemap: 117.495 secondes -> 4.633 secondes
Et le build final s’est terminé ainsi.
done in 110.344 seconds
De 21 minutes 52 secondes Ă 1 minute 50 secondes.
Ce n’est pas encore assez pour dire que le blog est rapide, mais au moins il n’est plus dans l’état où le build fait trop peur pour écrire.
Ce à quoi j’ai fait attention en corrigeant
Si l’on ne regarde que la vitesse, cette optimisation a l’air facile.
Mais ce qui demandait vraiment de l’attention, c’était de ne pas casser les fonctionnalités.
Sur un site multilingue en particulier, on peut facilement se réjouir d’un build plus rapide et créer ce genre de problèmes.
les liens de changement de langue mènent à des 404
hreflang pointe vers des URL inexistantes
les traductions en attente sont exposées comme alternate aux moteurs de recherche
sur mobile, les boutons About/langue disparaissent de nouveau
le calendrier reste vide
À la fin, j’ai donc combiné vérifications navigateur et vérifications automatiques.
Voici ce que j’ai vérifié.
production build réussi
les liens de changement de langue fonctionnent dans les 8 langues pour l'article de voyage en anglais
hreflang correct pour 8 langues + x-default
les articles dont la traduction est en attente retombent vers l'accueil de l'autre langue
sur mobile, About, 🇺🇸English et le calendrier s'affichent correctement
des URL multilingues représentatives répondent en 200
vérification exhaustive de la structure rendue de 2481 articles sources
parsing du JSON du calendrier vérifié
i18n post coverage errors: 0
Je n’ai pas lu les 2481 articles un par un à l’œil humain.
Mais au minimum, la vérification automatique a contrôlé que les fichiers de sortie existent, que la structure des articles est rendue, que les liens de langue ne sont pas cassés et que les données du calendrier existent.
C’était le cœur de ce travail.
Optimiser un build, ce n’est pas seulement réduire le temps. C’est expliciter à nouveau les contrats des fonctionnalités existantes.
Ce que j’ai appris cette fois
Jekyll a l’air simple parce que c’est un générateur de site statique.
Mais si Liquid commence Ă parcourir tout le site encore et encore, mĂŞme un site statique peut devenir franchement lourd.
Dans une structure multilingue surtout, les petites inefficacités deviennent immédiatement des multiplicateurs.
nombre de pages * nombre de langues * nombre de menus * nombre total d'articles
Quand ce genre de multiplication se cache quelque part, le build finit par exploser soudainement.
Ce que j’ai appris cette fois est simple.
Premièrement, ne pas répéter les mêmes données inline sur chaque page.
Deuxièmement, si les mêmes entrées donnent le même résultat, mettre l’include en cache.
Troisièmement, ne pas scanner tout le site depuis chaque page simplement pour vérifier si une URL existe.
Quatrièmement, ne pas traiter les exceptions au feeling : les garder dans des données.
Cinquièmement, après l’optimisation, vérifier les contrats fonctionnels par des tests automatiques.
Ce blog devient peu à peu moins un simple blog personnel qu’un système.
C’est bien, et c’est fatigant.
Mais au moins, cette fois, la fatigue avait du sens.
Faire passer un build de plus de 21 minutes à la tranche d’une minute a été, très concrètement, un grand tournant.
Le blog multilingue a maintenant au moins assez d’air pour continuer à grandir.
Laisser un commentaire