Aller au contenu principal
divagations - Retour à l'accueil

Implémenter une gestion des "brouillons" d'articles, partie 2

Guillaume Barbier

Temps de lecture : ~ 9 minutes

Hier, en travaillant sur le sujet, je me suis rendu compte que rien ne m'empêchait d'implémenter la mécanique de publication qui m'est familière[1]… si ce n'est la flemme de repasser sur toutes mes anciennes entrées de journal (environ une trentaine déjà !? 😲)

Bref aucune raison valide… si ce n'est "pourquoi je m'embête avec ça et je ne gère pas tout ça à la main ?"

Pourquoi je ne gère pas simplement mes brouillons à la main ?

Des méthodes existantes et simples

La question est légitime. En effet, il existe des méthodes natives et peu complexes pour bloquer la génération des pages inachevées.

Bloquer la génération en deux lignes

Bloquer la génération d'un article à partir de son front-matter se résume à deux propriétés (c'est d'ailleurs là-dessus que se base la mécanique que j'envisage d'écrire).

Pour empêcher la génération d'un article, il me suffit d'ajouter les lignes suivantes :

---
eleventyExcludeFromCollections: true
permalink: false
---

Et pour le publier dans un dossier protégé, ce n'est pas beaucoup plus compliqué :

---
eleventyExcludeFromCollections: true
permalink: /draft/journal/2026-02-21-titre-de-mon-article-ecrit-a-la-main/
---

Avertissement

Démenti : Il n'est pas inutile d'exclure une page "non-générée" des collections

Hier, j'évoquais le fait qu'il était probablement inutile d'exclure les articles qui ne seront pas générés des collections… Et bien, j'avais tort !

En retravaillant le mécanisme de publication, je me suis rendu compte que c'était au contraire très important.

En effet, peu importe l'état de la propriété permalink tant que le contenu du fichier est compilé par Eleventy, il sera inclus dans les collections (et remontera donc dans les listes d'articles). C'est valable même dans le cas permalink: false !

Bien isoler ses brouillons avec Git

Je pourrais aussi tout simplement m'éviter tout ça en étant très rigoureux dans l'usage de mes branches Git et en m'assurant que ma branche Main ne contienne jamais de brouillons.

Alors pourquoi implémenter une mécanique spécifique ?

Souplesse et rapidité

Même si je suis à l'aise avec Git, c'est toujours un truc de plus à prendre en compte avec parfois du temps perdu à manipuler et nettoyer l'arbre. Donc hors-jeu.

Et pour ce qui est de tout gérer manuellement avec mes deux petites propriétés… vous avez vu la gueule de eleventyExcludeFromCollections ?!! C'est la typo garantie ! … et flemme de copier-coller à chaque fois ! Donc hors-jeu aussi.

Sur cette seule base, je suis déjà convaincu de l'utilité de cette mécanique, mais il reste un dernier point pour finir de me convaincre…

Je ne suis pas tout seul

L'argument ultime pour l'implémentation de ce système, c'est que nous sommes deux ! Et surtout ma co-autrice n'est pas développeuse (et ne doit pas avoir à le devenir pour simplement écrire et publier un article).

Je lui ai expliqué les bases de Git et elle a le bagage technique nécessaire pour l'utiliser, mais jongler entre des branches Git quand on est pas habitué, c'est quand même un peu peu fatiguant, voir même effrayant (surtout quand le dev en face n'a pas mis en place de workflows clairs ou même juste quelques gardes-fous 😅)

Avec cette mécanique, j'espère ainsi éviter à Myriam de se poser trop de questions existentielles si elle souhaite déployer en mon absence (en dehors de choisir un client FTP et retrouver où sont ces satanés codes de connexion au serveur !)

Fini de tergiverser, maintenant on implémente !

Modifier le traitement des articles en fonction de leur status

Réduire la mécanique à certains types de fichiers

Hier j'avais utilisé un pre-processeur pour exclure globalement tous les fichiers avec l'option "draft" activée. Là je fais l'inverse, j'exclu les fichiers qui n'ont pas l'option "published"… et je n'ai pas envie d'avoir à ajouter ça sur tous les fichiers que j'ajoute !

Il y a des dossiers où cette mécanique est clairement plus utile que d'autre, donc je décide de restreindre la mécanique à ceux-ci : le dossiers "articles" et "journal"

C'est sur ces dossiers que la mécanique est la plus pertinente : c'est dans ces dossiers que le cas de figure "j'ai besoin de déployer mon site mais sans embarquer du contenu en cours de rédaction" risque de survenir régulièrement.

Les contenus créés en dehors de ces deux dossiers seront en général liés à des fonctionnalités et seront très probablement isolés sur leur propre branche jusqu'à ce qu'ils soient prêt… et dans le pire des cas, je peux simplement les exclure manuellement en ajoutant les bonnes propriétés.

Astuce

Des dossiers avec leur propres règles de réécriture

Pour ma mécanique, je vais devoir réécrire dynamiquement l'emplacement des fichiers finaux (avec permalink), sauf que j'ai déjà toute une mécanique de ce genre en place sur ces deux dossiers.

Ça aurait été une mauvaise nouvelle si j'avais essayé d'implémenter cette mécanique globalement, car il aurait alors fallu l'intégrer à plusieurs niveaux (globalement puis sur chaque dossier concerné[2]). Mais comme j'ai de toute façon décidé de me limiter à ces dossier, c'est au final une bonne nouvelle : j'ai déjà une structure dans laquelle intégrer ma règle.

Exclure et déplacer

J'enrichi donc mes dossiers "journal" et "article" avec la donnée calculée eleventyExcludeFromCollections :

// In journal.11tydata.js and articles.11tydata.js
export default {
  eleventyComputed: {
    eleventyExcludeFromCollections: (data) => {
      if (data.eleventyExcludeFromCollections) return true
      if (process.env.MY_ENVIRONMENT == "prod") {
        if (!data.published) return true
        if (data.published == "draft") return true
      } 
      return false
    }
  }
}

Avant d'appliquer ma mécanique, je fais bien attention à respecter toute définition manuelle de la donnée (au cas où un article serait explicitement exclu des collections pour… raisons). Ensuite je m'assure de n'exclure mes articles que en mode "générer pour la prod" (je me base sur la variable MY_ENVIRONMENT que j'ai ajouté hier à mon script build)

Et pour "terminer" la mécanique, je viens modifier la donnée calculée permalink :

// In journal.11tydata.js and articles.11tydata.js
export default {
  eleventyComputed: {
    permalink: function(data) {
      // Here, published status takes precedence on any manually set permalink (when building for prod)
      if(process.env.MY_ENVIRONMENT == "prod" && !data.published) return false
      // If the article is published, then we're building the requested permalink
      if (data.permalink) return data.permalink
      // Definition of the article path parts// Check if it should be published as a "draft"
      if(data.published == "draft") articleFolder = "/draft" + articleFolder

      return `${articleFolder}/${safeUrlDate}-${safeUrlName}/`
    }
  }
}

Mon système de publication est maintenant en place, avec ses trois niveaux de publication :

  • Non publié (l’état par défaut)
  • Publication "privée", en tant que brouillon (quand j'ajoute published: draft)
  • Publication "complète" (quand j'ajoute published: true)

Il ne me reste plus qu'à protéger mon dossier "draft" et en restreindre l'accès à mes relecteurs et relectrices.

Sécuriser mes pages "privées"

J'ai opté pour un hébergement somme toute classique, avec un serveur Apache. Il me suffit donc d'un fichier .htaccess dans le dossier protégé ainsi que d'un fichier .htpasswd pour stocker les identifiants et mot de passes que je veux autoriser.

Pour le .htaccess, rien de nouveau, je sais déjà comment l'intégrer avec eleventy, je me contente de récupérer le bout de code partagé dans la doc d'OVH(S'ouvre dans un nouvelle fenêtre) :

AuthName "Indicates your login(s)"
AuthType Basic
AuthUserFile "/home/your_ftp_login/root_folder/admin/.htpasswd"
Require valid-user

(Non je ne vous donnerai pas les logins et le path vers mon .htpasswd 😉)

Pour le .htpasswd par contre, ça a été un peu plus compliqué. Bon déjà je n'ai pas suivi la doc d'OVH pour encrypter mon mot de passe (pas envie de me lancer dans du PHP). À la place je suis allé sur un site qui propose une fonction d'encryptage en direct (pas non plus envie de regarder comment intégrer ça dans Eleventy) : Strong Htpasswd Generator – Hosting Canada(Opens in a new window)

Ensuite j'ai eu deux petites surprises :

  • les permalink remontent toujours à la racine
  • Eleventy n'aime pas les fichiers .htpasswd

J'écrivais mon permalink ainsi :

---
permalink: .htpasswd
---

Mais à cause du comportement des permalink, peut importe où je rangeais mon fichier Liquid, mes fichiers "HT" étaient générés à la racine. Il a fallu que j'intègre l'emplacement exact de chaque fichier pour obtenir le résultat attendu.

Mais autre surprise, pour je ne sais quelle raison, Eleventy ma lancé une erreur pour le fichier .htpasswd (erreur qui n'avait pas été provoquée pour aucun fichier htaccess). Apparemment Eleventy réclame dans ce cas précis une extension… Heureusement la solution est simple et se résume à l'ajout de la propriété eleventyAllowMissingExtension: true

Une fois ces petites surprise résolues, c'est bon, la protection fonctionne ! (Testée avec le brouillon de cet article.)

Ajuster le paramétrage de mon .htaccess

J'ai passé encore un peu de temps à "jouer" avec mon .htaccess :

  • Autoriser le parcours des fichiers du dossier "draft"
  • Améliorer l'affichage de ses contenus
  • Essayer de le sécuriser

J'ai voulu (ré)autoriser[3] le parcours des contenus du dossier "draft" pour nous simplifier la vie : plus facile d'aller chercher et copier l'URL d'une page qui ne remonte nul part si on peut accéder à son dossier parent et la liste de son contenu.

# ENABLE DIRECTORY VIEWS
Options +Indexes

Avec ça, facile de partager les URLs de partage à nos relecteurs et relectrices 😉

J'ai aussi trouvé quelques astuces pour améliorer la présentation grâce à cet article : Better Default Directory Views with HTAccess(Opens in a new window)

Enfin, j'ai envisagé de renforcer la sécurité de mon serveur en appliquant ce fragment de code trouvé sur le même site (dans Improve Security by Protecting .htaccess(Opens in a new window)) :

# STRONG HTACCESS PROTECTION
<Files ~ "^.*\.([Hh][Tt][Aa])">
 order allow,deny
 deny from all
</Files>

Mais au final il semble que ce n'était pas très utile, l'accès à mes fichiers "HT" étant déjà bloqué (je suspecte une config à la racine des serveurs mutualisés d'OVH… ce qui serait relativement sage, je suppose[4])

Bref. Je laisse tomber les raffinements pour aujourd'hui, satisfait de mon système de publication opérationnel 😎


  1. le classique "Mon nouvel article est en brouillon. Quand il est prêt, j'effectue une action qui le marque explicitement comme 'à publier'" ↩︎

  2. Car d'après mes tests, si des données calculées sont appliquées à plusieurs niveaux pour une même propriété, seule la fonction de plus bas niveau est déclenchée (il n'est donc pas possible de "chaîner" des calculs de données.) ↩︎

  3. autoriser car j'ai bloqué cet accès par sécurité à la racine ↩︎

  4. Je dis ça mais, bien sûr, je n'y connaît rien en cybersécu 😅 ↩︎