After Nuxt, What's Next? 💭

After Nuxt, What's Next? 💭

28 février 2022 | 17 mins de lecture

Publié sur dev.to et medium.com

Nouvel article, nouveau sujet, cette fois-ci je vous emmène au cœur de la JAMStack pour vous parler de SSG, SSR, SEO, au travers des frameworks Nuxt et Next, sans oublier d'évoquer le monde merveilleux d'UnifiedJS.

Vous l'aurez compris, ici je vais parler de... JavaScript (une fois de plus 😉). Mais avant cela, une petite introduction pour contextualiser les choses...

Précédemment...

Début d'année 2022, j'ai commencé à implémenter une fonctionnalité de traduction pour mon portfolio.

NB : À l'origine, mon portfolio n'était traduit qu'en anglais, et mon entourage n'avait de cesse de me demander que ce dernier soit disponible dans ma langue natale, c'est-à-dire le français.

Mon projet étant initialement développé à partir d'un framework JAMStack, je me suis orienté vers un plugin "i18n" déjà existant. Après l'avoir configuré, je me suis vite rendu compte que celui-ci ne convenait pas parfaitement à mon besoin. En effet, je souhaitais un mode de fonctionnement "hybride" me permettant de traduire simplement (via un système classique de "clés - valeurs"), mais aussi de pouvoir traduire par moi-même (notamment pour les articles). J'ai donc du (re)coder une partie de l'utilitaire afin d'arriver à un résultat convenable... Mais pour autant, loin d'être optimisé.

Suite à ce constat, j'ai entamé un travail de migration, car quitte à avoir plus de flexibilités, autant tester plusieurs autres technologies en détail ! Je suis donc passé de Gridsome à Next (en passant par Gatsby, puis Nuxt).

NB : Mon cœur balance depuis des années entre React et Vue, cette fois-ci je me suis laissé tenter par React (c'est aussi la librairie que j'utilise majoritairement au quotidien, donc un point de plus en faveur de celle-ci).

Ce chantier ma suivi jusqu'au mois de février (entre comparaisons, migration, implémentation de l'internationalisation, tests, etc...) Bref ! De quoi se faire plaisir et (re)découvrir des technologies modernes et performantes.

Je vous retranscris ici (sous forme de série), les quelques avantages et inconvénients que j'ai pu identifier à l'usage de chacun de ces frameworks.

WTF Is JAMStack!?

Pour rappel, la JAMStack est un environnement technique qui consiste à construire un site web / une application à partir de JavaScript, d'APIs réutilisables et de servir le tout au format HTML ("M" pour Markup) à l'aide d'un générateur de site statique.

Les données qui serviront à alimenter le site web / l'application peuvent être récupérées soit localement (via des fichiers Markdown par exemple), soit de manière distante, via les APIs des CMS. Le générateur de site statique construit ensuite une version stable (incluant l'ensemble des ressources et pages nécessaires) prêt à être déposé sur un service d'hébergement.

Cet environnement technique offre bien des avantages, tels qu'une meilleure réactivité (du fait de la récupération de toutes les ressources lors de la phase de build), une meilleure évolutivité (le développeur n'est pas contraint par une architecture lourde, il peut se concentrer sur le frontend), et surtout un meilleur référencement (chaque page peut gérer ses attributs relatif au SEO).

NB : Les frameworks JAMStack s'articulent autour du pattern PRPL, afin d'optimiser les performances des sites web / applications sur les smartphones et les appareils disposant d'une faible connexion internet.

Ep 1. Vue + JAMStack = Gridsome

Au cœur de cet écosystème se trouve le framework open-source Gridsome, lui-même propulsé par la communauté Vue. Comme pour ce dernier, il bénéficie d'un réseau de développeurs actifs et d'une documentation bien faite.

npm install -g @gridsome/cli
gridsome create my-portfolio

La CLI de Gridsome permet d'échafauder l'architecture de votre projet JAMStack très simplement. D'ailleurs, le véritable pouvoir de ce genre de framework réside dans son arborescence de fichiers / dossiers qui porte le routage au plus haut niveau.

<template>
  <Layout>
    <div class="post-title">
      <h1>{{ $page.post.title }}</h1>

      <PostMeta
        :post-date="$page.post.date"
        :time-to-read="$page.post.timeToRead" />
    </div>

    <div class="post">
      <div class="post__header">
        <g-image
          v-if="$page.post.coverImage"
          alt="Cover Image"
          :src="$page.post.coverImage" />
      </div>

      <div
        class="post__content"
        v-html="$page.post.content" />

      <PostTags :post-tags="$page.post.tags" />
    </div>
  </Layout>
</template>

<page-query>
  query Post($id: ID!) { post: post(id: $id) { content title date(format: "YYYY-MM-DD") description coverImage(width:
  720, blur: 10) tags path timeToRead } }
</page-query>

<script>
  import PostMeta from '~/components/PostMeta.vue';
  import PostTags from '~/components/PostTags.vue';

  export default {
    components: {
      PostMeta,
      PostTags
    },
    metaInfo() {
      return {
        title: this.$page.post.title,
        meta: [
          {
            name: 'description',
            content: this.$page.post.description
          }
        ]
      };
    }
  };
</script>

Gridsome possède une API (magique) au format GraphQL (ici entre les balises <page-query>) permettant de récupérer du contenu et de l'intégrer au composant, au travers de la variable $page. De plus, il embarque une partie de RemarkJS (🚨 #SpoilerAlert 🚨 Cf. Le Monde Merveilleux d'UnifiedJS) au sein de son API, pour transformer les fichiers Markdown vers le format HTML.

Ce framework inclut également la dépendance vue-meta pour la gestion des métadonnées. Il est donc très facile d'ajouter ou de mettre à jour les données responsables du bon référencement de votre site web, et cela pour chaque composant de type "page" ou "template".

Comme mentionné précédemment, la structure du projet à son importance, puisque les composants placés dans le dossier "pages", créeront leurs propres routes en fonction de leur nommage (en pratique, un fichier 404.vue créera une page /404). En revanche, pour la génération de pages à la volée, il convient plutôt d'utiliser le dossier "templates".

+-- content                     # *.md Are Here
+-- public                      # Static Files
+-- src
    +-- components
    +-- layouts
    +-- pages                   # Explicit Pages
    +-- templates               # Dynamic Page Templates
+-- gridsome.config.js
+-- gridsome.server.js
+-- package.json

Toujours dans l'architecture du framework, le fichier gridsome.server.js permet de manipuler l'API de Gridsome, notamment pour créer des pages dynamiques (basées sur les composants "templates"). Parmi les cas d'usage, il y a les dépendances spécifiques à Gridsome ; exemple avec les plugins "sources" qui chargent les données (de manière asynchrone) et les rendent disponibles depuis l'interface GraphQL.

Enfin, le fichier gridsome.config.js parle de lui-même, puisqu'il permet d'enrichir la configuration du projet, qu'il s'agisse du titre, de la description du site web (dans un contexte SEO), etc... Ou pour intégrer des librairies additionnelles (la prise en charge des locales "i18n" par exemple).

NB : C'est peut-être le point négatif de ce genre de technologie. Beaucoup de fonctionnalités clés en main proviennent de plugins (avec leur propre configuration). Malheureusement, si l'on cherche à aller plus loin avec une librairie, il n'y a pas d'autre moyen que de tirer le projet en local et (re)développer une partie du code ; sinon se contenter des fonctionnalités de base...

Gridsome est une petite pépite dans le monde du développement web. Il bénéficie d'une communauté solide, ainsi que de nombreux "starters" qui servent de base au développement d'un nouveau site web. Si vous débutez avec un framework JAMStack, il sera rapidement prêt à l'emploi, pour exposer des données locales (aux formats .md, .mdx) ou distantes, depuis une interface de CMS (Strapi, Forestry ou encore Contentful).

Il a répondu à mon besoin pendant 2 ans, mais maintenant il est temps de changer...

Ep 2. Gatsby, Le Magnifique ✨

Gatsby est le côté obscur de la force (si l'on considère Gridsome comme son côté lumineux). En d'autres mots, Gatsby est l'équivalent de ce dernier dans l'écosystème React.

npm install -g gatsby-cli
gatsby new

Tout comme son homologue, Gatsby dispose d'un outil de CLI permettant de construire un nouveau projet JAMStack. À la différence que celui-ci fonctionne avec un système de "questions - réponses". Ainsi, on peut choisir d'ajouter le support des fichiers Markdown, d'intégrer une librairie UI (styled-component / emotion), mais aussi de configurer l'usage d'un CMS.

Il possède beaucoup de concepts en commun avec Gridsome, notamment pour la gestion du routing à travers le dossier "pages", la dynamisation de pages avec la convention du dossier "templates", la récupération de données locales ou distantes via une API GraphQL, etc...

import React from 'react';
import { Helmet } from 'react-helmet';
import { graphql } from 'gatsby';
import { GatsbyImage } from 'gatsby-plugin-image';
import Layout from '@/components/Layout';
import PostMeta from '@/components/PostMeta';
import PostTags from '@/components/PostTags';

export default function Post({ data: { post } }) {
  const { frontmatter, fields } = post;
  const { childImageSharp } = frontmatter.coverImage;

  return (
    <>
      <Helmet>
        <title>{frontmatter.title}</title>
        <meta
          name="description"
          content={frontmatter.description}
        />
      </Helmet>

      <Layout>
        <div className="post-title">
          <h1>{frontmatter.title}</h1>

          <PostMeta
            postDate={frontmatter.date}
            readingTime={fields.readingTime}
          />
        </div>

        <div className="post">
          <div className="post__header">
            {frontmatter.coverImage && (
              <GatsbyImage
                alt="Cover Image"
                src={childImageSharp.gatsbyImageData}
              />
            )}
          </div>

          <div
            className="post__content"
            dangerouslySetInnerHTML={{ __html: post.html }}
          />

          <PostTags postTags={frontmatter.tags} />
        </div>
      </Layout>
    </>
  );
}

export const query = graphql`
  query Post($id: ID!) {
    post: markdownRemark(id: { eq: $id }) {
      html
      frontmatter {
        title
        date(formatString: "YYYY-MM-DD")
        description
        coverImage {
          childImageSharp {
            gatsbyImageData(quality: 90, width: 720, formats: [WEBP])
          }
        }
        tags
      }
      fields {
        slug
        readingTime {
          minutes
        }
      }
    }
  }
`;

Ici, on remarque l'usage d'une API GraphQL (à nouveau) pour injecter des données en tant que props de composant (même si la syntaxe diffère quelque peu de Gridsome, on retrouve globalement la même structure). Grâce à la dépendance gatsby-transformer-remark (🚨 #SpoilerAlert 🚨 Cf. Le Monde Merveilleux d'UnifiedJS), préalablement installée lors de l'interrogation de la CLI, le framework devient capable d'exploiter les fichiers au format .md.

NB : Il m'a aussi fallu ajouter le plugin gatsby-remark-reading-time pour obtenir le temps de lecture par article, là où Gridsome le faisait implicitement.

Ce framework supporte très bien les formats modernes d'images (WebP), idéal pour optimiser le temps de rafraichissement d'un site web. Pour ce qui est du SEO, il sera nécessaire de passer par une librairie supplémentaire (notamment react-helmet), afin d'appliquer les métadonnées sur les différentes pages.

Le point fort de Gatsby c'est son mode SaaS. Si l'on ne souhaite pas déployer son application sur un serveur web traditionnel (Apache / Nginx), il existe des solutions JAMStack alternatives, telles que Netlify ou Vercel, mais aussi... Gatsby Cloud ! Le framework possède son propre produit pour une expérience optimale ! 👌

J'utilise Gatsby depuis sa version 2.0 avec le projet Orluk Photography. Je n'ai jamais était déçu par cet outil, il prend relativement bien en charge TypeScript (mieux depuis sa version 3.0), et s'interface parfaitement avec un CMS (Strapi, je t'aime 💜). Mais, au vu des similitudes avec Gridsome, autant conserver ce dernier ; ou bien tester quelque chose de nouveau...

Ep 3. Nuxt : Un "Meta" Framework Pour Les Gouverner Tous !

Tout aussi populaire que Gatsby *, il y a Nuxt ! J'ai toujours voulu essayer ce framework, et il faut dire que les articles de Debbie O'Brien ont confirmé mon enthousiasme pour cette librairie de l'écosystème Vue.

Nuxt épouse parfaitement la philosophie JAMStack, mais il fait bien plus que cela. En effet, il dispose de trois modes de fonctionnement :

  • Le mode Single Page App (SPA pour les intimes) ;
  • Le mode "static" (SSG), permettant de construire l'application à l'aide d'un générateur de site statique ;
  • Le mode "universel", qui permet de rendre l'application via un serveur NodeJS.

Avec le rendu côté serveur, l'internaute accédera au site web plus rapidement qu'en mode CSR (Client Side Rendering). Le rendu côté client s'appuie sur le JavaScript pour fournir le HTML ; alors que le mode SSR (Server Side Rendering) fourni d'abord le contenu statique (à savoir le HTML), puis le JavaScript, etc... Mis à part les gains de performance, ce mode de fonctionnement permet aux robots d'indexation de parcourir le site web plus facilement (puisque les pages sont directement accessibles).

Bref ! Il était temps de s'attaquer à ce framework !!! 🔥

NB : La version 3.0 venant de sortir en bêta publique, j'ai préféré développer à partir d'une version stable ayant déjà fait ses preuves.

npx create-nuxt-app my-portfolio

Comme pour Gatsby, la CLI Nuxt est tout simplement géniale puisqu'elle permet d'initialiser un projet avec une configuration complète. On peut ainsi choisir : le langage JavaScript ou TypeScript, le mode SSG ou SSR, le framework CSS à utiliser (y compris TailwindCSS), le moteur de tests unitaires, l'implémentation de Prettier, etc...

Nuxt possède beaucoup d'atouts, notamment l'intégration de Vuex par défaut (permettant de gérer les données à l'aide du pattern "state management" pour les applications à l'échelle), mais surtout un système de navigation par fichier (qui n'est pas sans rappeler celui de Gridsome), avec le fameux dossier "pages".

En revanche, pour la récupération de données, c'est une autre histoire. Il n'y a pas d'API GraphQL sur laquelle se reposer. Cette fois-ci, il faut faire les choses from scratch ! Quoique...

<template>
  <Layout>
    <div class="post-title">
      <h1>{{ post.title }}</h1>

      <PostMeta
        :post-date="post.date"
        :reading-time="post.readingTime" />
    </div>

    <div class="post">
      <div class="post__header">
        <img
          v-if="post.coverImage"
          :src="post.coverImage"
          alt="Cover Image"
          width="720"
          height="405" />
      </div>

      <nuxt-content
        class="post__content"
        :document="post" />

      <PostTags :post-tags="post.tags" />
    </div>
  </Layout>
</template>

<script>
  import Layout from '~/components/Layout.vue';
  import PostMeta from '~/components/PostMeta.vue';
  import PostTags from '~/components/PostTags.vue';

  export default {
    components: {
      Layout,
      PostMeta,
      PostTags
    },
    async asyncData({ app, $content, params }) {
      const post = await $content(params.slug).fetch();
      return { post };
    },
    head() {
      return {
        title: this.post.title,
        meta: [
          {
            hid: 'description',
            name: 'description',
            content: this.post.description
          }
        ]
      };
    }
  };
</script>

Pour m'aider à accéder et lire mes fichiers Markdown (et les transformer en Markup), j'ai fait appel à un des nombreux modules de la communauté Nuxt, à savoir @nuxt/content. Dorénavant, grâce à une API accessible par la variable $content, je suis en capacité de récupérer l'avant-propos et le contenu de mes fichiers .md pour les utiliser dans mon <template>.

Mis à part cette 1er intégration, il m'a aussi fallu ajouter une dépendance pour l'alimentation du SEO (npm i vue-meta), une seconde dépendance pour la fonctionnalité de traduction (npm i vue-i18n), ainsi que des fonctions utilitaires (telles que le calcul du temps de lecture).

NB : Pour le calcul du temps de lecture, j'ai créé une simple fonction que j'ai ensuite injectée dans l'API $content, grâce à un "hook" dans la configuration de Nuxt.

import { readingTime } from './src/utils';

export default {
  // ...nuxt.config.js
  hooks: {
    'content:file:beforeInsert': document => {
      if (document.extension === '.md') {
        document.readingTime = readingTime(document.text);
      }
    }
  }
};

Après avoir correctement configuré mon environnement Nuxt, et (re)développer mes pages dynamiques, j'ai réalisé des tests de performance avec Google Lighthouse, et je me suis rendu compte que certains points pouvaient être optimisés, notamment pour la gestion des images (score ~= 70). Là encore, j'ai dû installer un autre module open-source (@nuxt/images ou nuxt-optimized-images), pour une prise en charge du format WebP.

Verdict ? Nuxt est vraiment cool ! Je suis tombé sous le charme de son mode SSR. Malheureusement il nécessite quelques ajustements (ici et là) pour être pleinement opérationnel / efficace. Okay, what's next...

Ep 4. What's Next? 💭 #SeasonFinale

J'ai (re)découvert Next pendant leur conférence d'octobre dernier. Il y a tellement de choses à dire sur ce framework...

NB : À commencer par le fait que le créateur de Svelte, Rich Harris, a rejoint les équipes de Vercel récemment 👍

Popularisé par React, ce framework est l'équivalent de Nuxt. Il bénéficie de concepts similaires, tel que la gestion de pages par le dossier du même nom. À la différence que les dépendances ajoutées à Next seront davantage des librairies JavaScript "standard" plutôt que des plugins liés au framework (après tout, React est une librairie JavaScript, pas un framework 😎).

npx create-next-app

Plus léger que ses homologues, l'outil de CLI génère simplement l'arborescence du projet (incluant react, react-dom et next). Next s'oriente sur un déploiement SSR plutôt que CSR (bien que possible avec la commande next export). Il va donc compiler toutes les ressources nécessaires pour ensuite les rendre côté serveur.

+-- content                     # *.md Are Here
+-- public                      # Static Files
+-- src
    +-- components
    +-- pages                   # Explicit Pages
    +-- services                # Data Fetching
    +-- utils
+-- next.config.js
+-- package.json

Ci-dessus, la structure que j'utilise pour mon projet portfolio. Il y a très peu de configuration dans le fichier next.config.js, j'y ai seulement inscrit mes locales pour ma fonctionnalité d'internationalisation, ainsi que la configuration du mode PWA (mais ceci est une autre histoire).

import Head from 'next/head';
import Image from 'next/image';
import Layout from '@/components/Layout';
import PostMeta from '@/components/PostMeta';
import PostTags from '@/components/PostTags';
import { getPostBySlug, getAllPostSlugs } from '@/services/contentService';
import { markdownToHtml } from '@/utils/markdownUtil';

export default function Post({ post }) {
  return (
    <>
      <Head>
        <title>{post.title}</title>
        <meta
          name="description"
          content={post.description}
        />
      </Head>

      <Layout>
        <div className="post-title">
          <h1>{post.title}</h1>

          <PostMeta
            postDate={post.date}
            timeToRead={post.timeToRead}
          />
        </div>

        <div className="post">
          <div className="post__header">
            {post.coverImage && (
              <Image
                alt="Cover Image"
                src={post.coverImage}
                width={720}
                height={405}
              />
            )}
          </div>

          <div
            className="post__content"
            dangerouslySetInnerHTML={{ __html: post.content }}
          />

          <PostTags postTags={post.tags} />
        </div>
      </Layout>
    </>
  );
}

export const getStaticProps = async ({ params: { slug } }) => {
  const post = getPostBySlug(slug, ['content', 'title', 'date', 'description', 'coverImage', 'tags', 'timeToRead']);
  const content = await markdownToHtml(post.content);

  return {
    props: {
      post: {
        slug,
        ...post,
        content
      }
    }
  };
};

export const getStaticPaths = async () => {
  const allPostSlugs = getAllPostSlugs();

  return {
    paths: allPostSlugs.map(slug => ({
      params: {
        slug
      }
    })),
    fallback: false
  };
};

Next ne dispose pas d'API GraphQL prêt à l'emploi, ni de modules pour l'exploitation des formats .md / .mdx ; c'est au développeur de coder les fonctions qui lui sont nécessaires. Grâce à l'usage de NodeJS, et du combo gagnant de ses modules fs et path, il est possible d'accéder au système de fichiers. Ensuite, il va falloir opérer certaines transformations avec RemarkJS (🚨 #SpoilerAlert 🚨 Cf. Le Monde Merveilleux d'UnifiedJS) pour exposer le contenu des fichiers Markdown au format HTML.

import fs from 'fs';
import join from 'path';
import matter from 'gray-matter';
import { getReadingTime } from '@/utils';

export const getPostBySlug = (slug, fields = []) => {
  const realSlug = slug.replace(/\.md$/, '');
  const postsDir = path.join(process.cwd(), 'content');
  const fullPath = path.join(postsDir, `${realSlug}.md`);
  const file = fs.readFileSync(fullPath, 'utf-8');
  const { data, content } = matter(file);

  const item = {};

  fields.forEach(field => {
    if (field === 'slug') {
      item[field] = realSlug;
    }

    if (field === 'content') {
      item[field] = content;
    }

    if (field === 'timeToRead') {
      item[field] = getReadingTime(content);
    }

    if (typeof data[field] !== 'undefined') {
      item[field] = data[field];
    }
  });

  return item;
};

Après avoir expérimenté Gridsome, Gatsby et Nuxt, c'est un peu déroutant de ne pas avoir de fonction pour la gestion des données directement disponible depuis un import... Mais c'est finalement une bonne chose, puisqu'on comprend mieux ce qu'il se cache sous le capot.

Pourtant, ce métaframework React m'a offert la meilleure expérience de développement ! En plus d'avoir un système de routing complet, Next embarque également le composant <Head /> permettant d'enrichir les métadonnées des pages de l'application. De plus, grâce à son composant <Image /> (et non pas <img>), il offre une bonne optimisation dans la gestion des formats JPEG, PNG et... WebP, pour ainsi obtenir un meilleur score sur Google Lighthouse.

NB : Propulsé par Vercel, il est recommandé de déployer son application sur le service du même nom, afin d'avoir un aperçu en détail des performances de votre site web.

Là où Next m'a le plus surpris, c'est lors de la compilation du projet (next build). Depuis sa version 12.0, le framework a amélioré sa manière de construire sa version de production en s'appuyant sur le langage Rust, avec la librairie Speedy Web Compiler (plutôt que Babel). Cela se traduit par un gain de temps considérable (de 3 à 5 fois plus rapide par rapport à la version précédente). Je ne peux que vous le recommander !

Le Monde Merveilleux d'UnifiedJS #SpinOff

Durant ce travail de migration, j'ai pris le temps de découvrir ce qu'est réellement UnifiedJS. Cet écosystème regroupe plus d'une centaine de plugins permettant de manipuler du contenu. Qu'il s'agisse de <html>, des formats .md / .mdx ou encore de textes brut, les librairies open-source d'UnifiedJS sont capables de parcourir chacun de ces formats (à l'aide d'une syntaxe d'arbre) et d'y automatiser certaines tâches, tel que le contrôle syntaxique, l'interprétation de morceaux de code, la transformation de nœuds, ou encore la minification.

Parmi ce regroupement, on retrouve :

  • RemarkJS, pour le traitement de fichiers Markdown
  • RehypeJS, pour le traitement de fichiers HTML
import { remark } from 'remark';
import directive from 'remark-directive';
import gist from './remarkGist';
import gfm from 'remark-gfm';
import html from 'remark-html';
import prism from 'remark-prism';

export const markdownToHtml = async markdown => {
  const result = await remark().use(directive).use(gist).use(gfm).use(html).use(prism).process(markdown);

  return result.toString();
};

Dans l'exemple ci-dessus, j'utilise RemarkJS pour transformer le contenu d'un fichier .md (##Hello, **World**) au format HTML (<h2>Hello, <strong>World</strong></h1>). J'ajoute aussi la prise en charge du sucre syntaxique de GitHub (GFM) afin de supporter les tableaux et les listes de tâches. Enfin, j'utilise le plugin Prism pour colorer les portions de code (par langage), en fonction d'un thème CSS.

import { visit } from 'unist-util-visit';

export default function remarkGist() {
  return (tree, file) => {
    visit(tree, node => {
      if (node.type === 'textDirective' || node.type == 'leafDirective' || node.type === 'containerDirective') {
        if (node.name !== 'github') return;

        const data = node.data || (node.data = {});
        const attributes = node.attributes || {};
        const id = attributes.id;

        if (node.type === 'textDirective') file.fail("Text directives for 'GitHub' not supported", node);
        if (!id) file.fail('Missing gist ID', node);

        data.hName = 'iframe';
        data.hProperties = {
          src: `https://gist.github.com/${id}`,
          width: 720,
          height: '100%',
          frameBorder: 0
        };
      }
    });
  };
}

Il est possible de développer vos propres fonctions de transformation, pour la prise en charge des formats vidéo ou encore l'ajout de Snippets provenant de GitHub / GitLab, etc... Toujours dans l'exemple, j'utilise un plugin me permettant d'interpréter les directives, puis je transforme ceux correspondant au type ::github en récupérant le Gist (depuis son identifiant / URL) et en l'embarquant dans une balise <iframe>. Avec RehypeJS, j'aurais aussi pu récupérer le code (au format RAW) pour le passer entre les balises <pre> et <code>. Tout est possible avec UnifiedJS !

Ce "monde merveilleux" est soutenu par la communauté JAMStack, avec des contributeurs comme Netlify, Vercel ou encore Gastby. Je vous conseille vivement d'aller vous y aventurer (si ce n'est pas déjà fait au travers de plugins "magiques"). N'oubliez pas de vous équiper de vos deux meilleurs outils : RemarkJS et RehypeJS ! 🧑‍💻