Les articles (et les entrées de journal) ont elles aussi besoin d'un peu d'optimisation.
Métadonnées
En travaillant sur les articles, je me suis souvenu qu'il manquait quelques métadonnées, pour les articles mais aussi pour toutes les autres pages :
- les métadonnées [Open Graph]{https://ogp.me/}{lang=en} pour le partage des articles sur les réseaux (oui, je veux des jolis posts)
- la description de la page pour le SEO (j'ai déjà la balise
<title>) - les métadonnées pour l'icône de favoris (la favicon)[1]
En théorie, la tâche est simple, il faut juste faire attention à ne pas générer les métadonnées de description (celle de l'Open Graph et la générique) si je n'ai fourni aucune description pour la page…
Mais l'usage des Open Graph est plus complexe qu'il n'y parait, principalement à cause de leur documentation lacunaire, qui se concentre sur quelques détails techniques et survole très largement les usages. Le cas le plus gênant est celui de og:type :
Open graph et types de document
La spécification nous liste un certains nombre de valeurs à utiliser… sans jamais les relier à des cas concrets d'usages. On se retrouve donc à écumer le web à la recherche d'infos et de conseils sur les implémentations faites d'Open Graph (côté réseaux ou côté sites), en essayant de séparer les bêtises mal informées des sources fiables.
Ce que je crois avoir compris[2], c'est que la valeur website est à utiliser comme valeur de repli, mais uniquement quand il n'y a pas de meilleur type à appliquer. Et pour ce blog, je vois deux valeurs qui pourraient être plus adaptées, pour certaines pages :
articleprofile
La valeur profile peut être utile pour les pages "à propos de nous", mais je revisiterais cette valeur quand je travaillerais sur ce type de contenu.
Astuce
Définir le type de contenu
Pour définir la valeur de og:type, je réutilise la donnée contentType (j'évite ainsi une double saisie). Pour rappel, j'ai initialement créé cette donnée pour définir le balisage sémantique de la page.
Open graph : enrichir les données des articles
Dans l'immédiat, la valeur qui m'intéresse est article, que je vais utiliser pour mes articles et entrées de journal. Selon la documentation Open Graph, si j'applique ce type, je dois ajouter plusieurs métadonnées supplémentaires :
article:published_timearticle:modified_timearticle:expiration_timearticle:authorarticle:sectionarticle:tag
Pour la gestion des dates, je reprends mon filtre "dateForRobots" pour passer les dates au format ISO 8601(S'ouvre dans un nouvelle fenêtre) et mettre en place des stratégies de gestion spécifiques pour article:modified_time et article:expiration_time :
| Métadonnée | Gestion de l'information |
|---|---|
article:modified_time |
Je me base sur une nouvelle donnée updateDates dans laquelle j'attend une liste de dates (les modifications successives) au format yyyy-mm-dd et j'utilise la dernière valeur de la liste. |
article:expiration_time |
Cette donnée est plus compliquée à gérer. Je veux une date qui se définisse automatiquement (par exemple, 2 ans après la dernière modification) mais que je puisse surcharger automatiquement voir désactiver… Dans l'immédiat, je vais juste intégrer ce qu'il faut pour pouvoir utiliser la donnée quand elle est définie manuellement et je travaillerais un autre jour sur la mécanique d'automatisation. |
Pour article:section, je me contente de différencier articles et journal de bord. Pour cela, j'ajoute une propriété "section" dans mes fichiers de données content/journal/journal.11tydata.js et content/journal/articles.11tydata.js (qui s'appliquera ainsi à toutes les pages créées dans ces dossiers).
Avertissement
Autres remarques sur les métadonnées Open Graph
og:locale: je ne suis pas sûr que celle-ci soit nécessaire mais, dans le doute, je l'ai intégrée. (Il faudra quand même que je teste une fois le site mis en ligne.)og:image: Je ne l'ai pas encore intégrée car je ne suis pas encore totalement certain de comment je gérerais les assets visuels sur le site.
Métadonnées, conclusion
Au final, mon bloc de métadonnées comporte pas mal de contenus conditionnels :
<!-- In _layouts/base.html -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{title}} | {{base.titleSuffix}}</title>
{% case contentType %}
{% when "article" %}
<meta property="og:type" content="article">
<meta property="article:author" content="{{author.name}}">
<meta property="article:published_time" content="{{page.date | dateForRobots}}">
{% if updateDates.last %}
<meta property="article:modified_time" content="{{updateDates.last | dateForRobots}}">
{% endif %}
{% if expirationDate | dateForRobots %}
<meta property="article:expiration_time" content="{{expirationDate | dateForRobots}}">
{% endif %}
{% if section %}
<meta property="article:section" content="{{section}}">
{% endif %}
{% if tags %}
<meta property="article:tag" content="{{tags | join: ' '}}">
{% endif %}
{% when "profile" %}
<meta property="og:type" content="profile">
<!-- profile metadata to be completed -->
{% else %}
<meta property="og:type" content="website">
{% endcase %}
<meta property="og:url" content="{{ base.url }}{{ page.url }}">
<meta property="og:title" content="{{ title }}">
<meta property="og:site_name" content="{{ base.titleSuffix }}">
<meta property="og:locale" content="{{lang | default: base.lang}}">
{% if description %}
<meta name="description" content="{{ description }}">
<meta property="og:description" content="{{ description }}">
{% endif %}
<!-- links to stylesheet and favicons omitted -->
<meta name="generator" content="{{ eleventy.generator }}">
</head>
Astuce
Faire la "pub" d'Eleventy
J'ai aussi ajouté une métadonnée qui signale qu'Eleventy a été utilisé pour générer ce site, suivant les conseils donné dans cet article : You should add a generator tag to your Eleventy site(S'ouvre dans un nouvelle fenêtre)
Navigation inter articles
Cette navigation est très simple mais comporte une logique que je souhaite sortir de mes templates. En effet, je dois vérifier la présence de liens précédents et/ou suivants avant de générer la nav (et éviter de générer une nav vide) :
{% liquid
assign previousPost = collections.log-entry | getPreviousCollectionItem
assign nextPost = collections.log-entry | getNextCollectionItem
assign showNav = false
if previousPost
assign showNav = true
elsif nextPost
assign showNav = true
endif
%}
{% if showNav %}
<nav class="article-nav" aria-label="Autres entrées">
{% if previousPost %}
<a
href="{{ previousPost.url }}"
rel="prev"
class="previous-article">
<span aria-hidden="true"><</span>
{{ previousPost.data.title }}
</a>
{% endif %}
{% if nextPost %}
<a
href="{{ nextPost.url }}"
rel="next"
class="next-article">
{{ nextPost.data.title }}
<span aria-hidden="true">></span>
</a>
{% endif %}
</nav>
{% endif %}
Je décide donc de l'isoler dans son propre composant et de dynamiser le choix de la collection et le nom accessible de la navigation pour me permettre de la réutiliser aussi bien dans les entrées de journal que dans les articles.
Là, je tombe sur deux écueils :
- la balise Liquid
{% include %}est dépréciée et je dois utiliser à la place{% render %}pour inclure mon composant, une balise qui ne transmet plus automatiquement tout son contexte d'appel et force à expliciter chaque donnée transmise au code appelé.
En soi, ce n'est pas un grand problème, c'est même un cas de figure assez commun, mais cela nous amène au deuxième écueil. - Dynamiser la sélection de la collection à utiliser pour trouver les liens précédents/suivants
Au début, j'essayais de passer directement la collection à utiliser au composant : {% render 'prev-next-nav', collection: collection.log-entry %} mais la cascade d'erreur obtenue était assez violente. En effet, je ne peux effectuer le filtre getPreviousCollectionItem (ou son équivalent pour le lien suivant), avec juste la collection utilisée. Ce filtre a besoin de l'objet collections entier, et aussi d'ailleurs de l'objet page (sans lequel il ne peut identifier la page courante).
J'ai passé pas mal de temps à me dépatouiller de tout ça, mais j'ai finalement réécris la logique ainsi (sans trop changer le code derrière) :
<!-- In _includes/prev-next-nav.liquid -->
{% liquid
assign category = category | default: "all"
assign previousPost = collections[category] | getPreviousCollectionItem: page
assign nextPost = collections[category] | getNextCollectionItem: page
assign showNav = false
if previousPost
assign showNav = true
elsif nextPost
assign showNav = true
endif
%}
Je l'appelle ensuite ainsi :
<!-- In _layouts/article.html -->
{% render "prev-next-nav",
navName: "Autres articles"
collections: collections
page: page
category: "article"
%}
<!-- In _layouts/log-entry.html -->
{% render "prev-next-nav",
navName: "Autres entrées de journal"
collections: collections
page: page
category: "log-entry"
%}
Table des matières
Pas de grande surprise ici, il faut juste que je passe le contenu de la page en option a mon mini-composant (qui est surtout une capsule pour utiliser le filtre "toc"). Je pense juste à ajouter une propriété "id" afin de pouvoir différencier l'id utilisé dans le composant, au cas où je voudrais utiliser plusieurs tables des matières dans une même page (car les ID doivent être uniques par page).
Avec cette dernière touche, la structure de mes pages est à peu près terminée. (Il reste encore le design et probablement pleins de petits ajustements.)
Je devrais plutôt dire les favicons, car pour garantir un bon affichage de cette icône pour tous les usages sur tous les appareils, il est nécessaire d'en fournir plusieurs versions :
↩︎<!-- default favicon --> <link rel="shortcut icon" type="image/x-icon" href="/_src/img/favicon/favicon.ico"> <link rel="icon" type="image/png" sizes="32x32" href="/_src/img/favicon/favicon-32x32.png"> <!-- google tv favicon --> <link rel="icon" type="image/png" sizes="96x96" href="/_src/img/favicon/favicon-96x96.png"> <!-- for android mobile devices --> <link rel="icon" type="image/png" sizes="192x192" href="/_src/img/favicon/favicon-192x192.png"> <!-- for apple mobile devices --> <link rel="apple-touch-icon" type="image/png" sizes="152x152" href="/_src/img/favicon/favicon-152x152.png"> <link rel="apple-touch-icon" type="image/png" sizes="120x120" href="/_src/img/favicon/favicon-120x120.png"> <link rel="apple-touch-icon-precomposed" type="image/png" sizes="152x152" href="/_src/img/favicon/favicon-152x152.png"> <link rel="apple-touch-icon-precomposed" type="image/png" sizes="120x120" href="/_src/img/favicon/favicon-120x120.png">Ai-je vraiment bien compris ? Ou suis-je en train de produire une énième "bêtise mal informée" ? Si c'est le cas, j'espère qu'une âme charitable m'en informera. ↩︎