Hugo et Netlify

Cette version du blog est générée avec Hugo, et l’hébergement est assuré par Netlify. La prise en main de Hugo, et la construction d’un site sont relativement aisée, mais le passage d’un développement local à une déploiement sur Netlify peut réserver quelques surprises. Voici quelques points à prendre en compte pour un déploiement sans problème.

Hugo et Netlify

Ces points s’adressent principalement aux personnes qui développent leur propre thème, ou qui adaptent un thème existant.

Gestion du thème

Installation

Comme il est indiqué dans la documentation du Hugo, l’installation d’un thème ne peut pas se faire avec la commande git clone habituelle. Netlify ne pourra déployer le site. La méthode préconisée consiste à utiliser les sous-modules (commande git submodule). Je vous renvoie à l’article Révisions GIT: épisode 6.

Exemple avec le thème Universal

$ cd themes
$ git submodule add https://github.com/devcows/hugo-universal-theme

Modifications du thème

Que vous soyez l’auteur ou non du thème, vous serez amené à le mettre à jour régulièrement, en parallèle des mises à jour du site lui-même. Dans ce cas, il faut garder à l’esprit que nous n’avons pas un dépôt GIT mais deux : un pour le site, et un pour le thème :

  • Si nous sommes dans l’un des répertoires du thème, nous sommes dans le dépôt du thème (dépôt principal),
  • Si nous sommes dans l’un des répertoires du site, nous sommes dans le dépôt du site (dépôt secondaire).

Les modifications du thème d’une part, et celle du contenu, d’autres part, sont donc dissociées.

Comme nous en l’avons déjà décrit dans l’article Révisions GIT: épisode 6, le cycle des modifications d’un thème doit suivre les étapes suivantes :

  1. Au niveau du thème : Effectuer les modifications, ainsi que les opérations sur le dépôt git local (git fetch, git add/commit, …),
  2. Effectuer un git push des modifications du thème vers le dépôt distant,
  3. Intégrer les modifications du thème, au dépôt du site (dépôt principal), avec les commandes git add, et git commit,
  4. Effectuer un git push du dépôt principal.

L’oubli du point 2 conduira à un échec du côté de Netlify, comme le montre la figure 1.

Figure 1 : Oubli du push des modifications du thème
Figure 1 : Oubli du push des modifications du thème

Publication

Avec la plateforme Netlify, la génération et la publication des pages du site peuvent être complètement automatisées (Figure 2).

  • Netlify s’intégre à la plateforme GitHub,
  • Un pull request sur une branche GitHub déclenche la génération du site, et sa publication en mode « prévisualisation »,
  • Une fusion de cette branche vers la branche main ou master, déclenchera la génération du site, et sa publication finale.

Figure 2 : Intégration GitHub / Netlify
Figure 2 : Intégration GitHub / Netlify

En pratique :

#Nous sommes dans le répertoire du thème
$ pwd
	/.../.../EGE-Blog/themes/Minimal-Blank
# Edition de la feuille de styles
$ edit styles.css
$ git commit -a -m "Add styles for footnotes"	# Commit du changemen au niveau du thème
$ git push oririn main			                # On pousse les modifs au niveau du dépôt distant

# On change de répertoire pour se placer au niveau du site
$ cd ../..
# Status du dépôt local du site
$ git status
	On branch git_hugo   # Nous sommes dans la branche git_hugo
	Your branch is up to date with 'origin/git_hugo'.

	Changes not staged for commit:
	  (use "git add <file>..." to update what will be committed)
	  (use "git restore <file>..." to discard changes in working directory)
			modified:   themes/Minimal-Blank (new commits)

# Validation des modifications du thème.
$ git commit -a -m "Theme update"	
	[git_hugo 21f44f5] Update of the theme
	 1 file changed, 1 insertion(+), 1 deletion(-)

# Pousser les modifications de la branche git_hugo vers le dépôt distant
$ git push origin git_hugo

GitHub qui vient de recevoir une mise à jour d’une branche, va proposer de créer une pull request (figure 3).

Figure 3 : Github propose un pull request
Figure 3 : Github propose un pull request

La création de cette pull request va déclencher la génération du site, côté Netlify (figures 4 et 5)

Figure 4 : Pull request créée dans GitHub
Figure 4 : Pull request créée dans GitHub

Figure 5 : Génération sur Netlify
Figure 5 : Génération sur Netlify

Une fois que la génération du site est complète, et le site disponible en mode preview (génération sans erreur) dans Netlify, Github affiche les résultats des vérifications effectuées (figure 6).

Figure 6 : Status de la preview dans Github
Figure 6 : Status de la preview dans Github

Il ne reste plus qu’à confirmer la fusion en appuyant sur Merge pull request. La fusion de la branche en cours vers la branche main/master va déclencher la génération et la publication finale du site (figure 7)

Figure 7 : Publication du site sur Netlify
Figure 7 : Publication du site sur Netlify

Manipulation de fichiers

Au niveau du thème, nous avons parfois besoin de manipuler des fichiers, comme pour gérer les images, par exemple. Dans ce cas, il y a plusieurs risques potentiels :

  • Des erreurs au niveau des chemins, qui sont différents entre l’arborescence de développement, et l’arborescence générée,
  • Des problèmes de casse. Ces problèmes se posent particulièrement si vous développez sous Windows. Ce système ne fait pas de différence entre minuscule et majuscule, contrairement aux systèmes Linux de l’hébergeur. Donc, nous pouvons avoir un site qui fonctionne parfaitement sur notre machine, et ne plus fonctionner du tout en mode preview, ou production,
  • Des chemins « codés en dur » (hardcoded), qui doivent être ensuite modifiés en fonction des environnements.

Les conseils pour réduire ces risques sont

  • Utiliser, autant que possible, les fonctions de Hugo pour accéder aux données,
  • Utiliser la même casse pour toute l’arborescence (minuscule par exemple),
  • Respecter les conventions de Hugo pour la gestion des données (utilisation des répertoires /data, /assets, ou /static).

Images

Un exemple d’opérations sur les images. Ici, à partir d’un chemin relatif, nous utilisons la fonction GetMatch pour obtenir le chemin complet de l’image. A partir de là, nous avons un accès aisé aux caractéristiques de l’image, et aux fonctions de manipulations.

{{- $size := 320 -}}
{{- $imgpath := <relative path to the image> -}}
{{- $image = .Resources.GetMatch $imgpath -}}
{{- with $image -}}
    {{- $width := int .Width -}}
    {{- $img_resized := $image.Resize (printf "%dx" $size) -}}
{{- end -}}

Manipulation des données

Dans le thème Goyat, j’utilise un fichier pour décrire la configuration du pied de page. Ma structure de fichier est la suivante :

  • J’ai un fichier /data/footer.json qui contient la configuration voulue par l’utilisateur du thème,
  • Le fichier /themes/goyat/data/default_params.json contient les valeurs par défaut (valeurs prises si elles ne figurent pas dans le fichier /data/footer.json.
├── config.toml
├── archetypes
├── content
├── data
|	└── footer.json
├── layouts
├── static
└── themes
    └── <nom du thème>
        ├── assets
        ├── data
	|	└── default_params.json
        ├── i18n
        ├── layouts
        └── theme.toml

Le code suivant

  • parcourt le fichier /data/footer.json,
  • récupère des paramètres dans ce fichier, comme par exemple .limitedwidth, ou .columns,
  • et va chercher les valeurs par défaut si nécessaire, comme site.Data.default_params.widgets.areas.limitedwidth par exemple.
{{- range sort site.Data.footer.Rows "weight" -}}
	<div class="footer-{{- .name }}">
		{{- $limit := cond (.limitedWidth | default site.Data.default_params.widgets.areas.limitedwidth) "limit-width" "" -}}
		<div class="container {{ $limit -}}">
			{{- $columns := .columns | default site.Data.default_params.widgets.areas.columns -}}
			... ...
		</div>
	</div>
{{- end -}}

Autre exemple, avec la lecture d’un fichier JSON.

{{- $filepath := path.Join "data" .params.filename -}}
{{- if os.FileExists $filepath -}}
    {{- $data := getJSON $filepath -}}
    {{- with $data -}}
        {{- range sort $data.posts "visit" "desc" -}}
			... ...
		{{- end -}}
	{{- end -}}
{{- end -}}

La page de contact

Pour la page de contact, j’utilise pour l’instant les mécanismes offerts par Netlify : ce n’est pas hyper-ergonomique, mais c’est facile à mettre en place. La page fonctionne très bien. Cependant, si les SPAMS sont bien identifiés (grâce à Akismet), nous les retrouvons tous dans la rubrique SPAM du formulaire, et l’interface Netlify ne propose pas de bouton pour les effacer tous en même temps. Il faut donc y aller régulièrement, et les effacer un par un, ce qui est plutôt fastidieux.

Je conseille donc de mettre en place, assez rapidement, l’une des deux méthodes de filtrage proposées par Netlify : Soit le pot de miel (honey pot), soit le Captcha.

  • Le pot de miel consiste à mettre en place un champ caché dans le formulaire : si le champ est saisi, alors il ne s’agit pas d’un humain,
  • Le challenge Captcha proposé par la plateforme est le reCaptcha 2.

J’ai mis en place, dans un premier temp, le pot de miel : le bilan est positif, mais il laisse encore passer pas mal de SPAMS (un peu plus de 1 par heure). Après quelques jours. Je suis donc passé à Captcha 2, dans sa version simplifiée. Les résultats sont beaucoup plus efficaces.

Voici le formulaire de contact de ce blog :

 <form id="contact-form" name="contact" method="POST" data-netlify-recaptcha="true" data-netlify="true">
	<div class="form-group">
	    <label for="form-your-name">{{- i18n "your-name" -}}</label>
	    <input type="text" class="form-control" id="form-your-name" name="form-your-name">
	</div>
	<div class="form-group">
	    <label for="form-your-email">{{- i18n "email-address" -}}</label>
	    <input type="email" class="form-control" id="form-your-email" name="form-your-email">
	</div>
	<div class="form-group">
	    <label for="form-subject">{{- i18n "subject" -}}</label>
	    <input type="text" class="form-control" id="form-subject" name="form-subject">
	</div>
	<div class="form-group">
	    <label for="form-msg">{{- i18n "your-message" -}}</label>
	    <textarea class="form-control" id="form-msg" name="form-msg" rows="5"></textarea>
	</div>
	<div data-netlify-recaptcha="true"></div>
	<button type="submit" class="btn btn-primary contact-button" value="submit">{{- i18n "submit" -}}</button>
</form>

L’activation de Captcha2 se fait simplement, en ajoutant l’attribut data-netlify-recaptcha="true" au formulaire.

Figure 8 : Le formulaire de contact
Figure 8 : Le formulaire de contact

Gestion des déploiements

Dans un paragraphe précédent, nous avons vu que Netlify propose deux environnements: preview et Production. Au final, avec notre poste de travail, nous avons 3 environnements

  • L’environnement de développement,
  • L’environnement de preview, celui dont la construction se déclenche avec les pull-request
  • L’environnement de production.

En fonction de ces trois environnements, des éléments peuvent différer :

  • Les articles à afficher sont différents. Nous pouvons avoir des options comme --buildFuture pour les environnements de dev et preview
  • Les urls sont différentes : localhost pour le développement, une url avec l’identifiant de déploiement pour le preview, et le nom du domaine du site pour la production

Si l’on se contente du fichier de configuration du site (config.toml), avec des paramètres comme baseURL, nous aurons des résultats dans certains environnements, et quelques limitations dans d’autres.

Une autre solution consisterait à utiliser les paramètres de déploiement dans l’interface de Netlify.

Figure 9 : hugo_netlify_parameters.png
Figure 9 : hugo_netlify_parameters.png
Mais cette option a ses limites : comment faire varier les paramètres en fonction de l’environnement ?

La solution la plus simple et la plus souple consiste à

  • Enlever les valeurs du fichier config.toml qui peuvent varier en fonction de l’environnement (par exemple baseURL=""),
  • Passer ces valeurs à NetLify via un autre fichier de configuration netlify.toml, que l’on place à la racine du site.

A titre d’exemple, voici le fichier de configuration de mon blog :

[context.production]
command = "hugo --minify --baseURL=$URL"

[context.production.environment]
HUGO_VERSION = "0.101.0"
HUGO_ENV = "production"

[context.deploy-preview]
command = "hugo --buildFuture --baseURL=$DEPLOY_URL --minify"

[context.deploy-preview.environment]
HUGO_VERSION = "0.101.0"
HUGO_ENV = "preview"

Au passage, l’autre avantage d’utiliser cette méthode, est qu’elle offre suffisamment de souplesse pour réaliser des tests de versions, ou de paramètres en mode preview, avant d’effectuer ces changements en mode production.

Conclusion

Netlify propose une solution très efficace pour le déploiement automatique d’un site Hugo.

Si vous êtes débutant avec Hugo, et vous avez l’intention d’utiliser Netlify comme solution d’hébergement, je vous conseille de démarrer les déploiements asez tôt dans votre processus de développement. Cela vous permettra de repérer rapidement d’éventuelles différences de comportement entre votre environnement de développement, et l’environnement Netlify, et donc d’ajuster votre code rapidement.

Les documentations Hugo et Netlify sont claires et assez exhaustives pour répondre à la plupart des problèmes rencontrés.

Références

Commentaires