Aller au contenu

Rendre le Markdown et le code côté serveur

Comment ce site transforme le Markdown du CMS en HTML sûr et coloré avant qu'il n'atteigne le navigateur, avec unified, rehype-sanitize et Shiki.

Dahmane Abdelmalek

27 oct. 20263 min de lecturePartie 3 sur 3

Les études de cas, les descriptions de postes et les articles comme celui-ci sont écrits en Markdown dans le CMS. Transformer du Markdown en HTML, c'est facile. Le faire de façon sûre, avec du code coloré, et sans obliger chaque visiteur à télécharger un analyseur et un coloriseur demande un peu plus de réflexion.

Faire le travail une fois, sur le serveur

Le navigateur ne voit jamais de Markdown. Les fonctions serveur récupèrent le contenu dans Strapi, le transforment en HTML et envoient le résultat. Les champs qui contiennent du HTML prêt à l'emploi finissent par Html (achievementsHtml, descriptionHtml) : un composant sait d'un coup d'œil qu'il peut l'afficher tel quel.

Les bénéfices s'additionnent : ni analyseur Markdown ni coloriseur dans le JavaScript envoyé au navigateur, rien à calculer sur un téléphone lent, et un seul endroit où le contenu devient du HTML.

Faire le travail coûteux une seule fois, là où il coûte le moins, et envoyer le résultat.

La chaîne de traitement

Le rendu est une chaîne unified : analyser le Markdown, ajouter les extensions de GitHub comme les tableaux, convertir en HTML, nettoyer, colorer le code, puis tout remettre sous forme de texte.

lib/markdown/render.server.ts
function createMarkdownProcessor(highlighter: Highlighter) {
  return (
    unified()
      .use(remarkParse)
      .use(remarkGfm)
      // Raw HTML in the Markdown is dropped, not passed through.
      .use(remarkRehype)
      // Defence in depth: strips unsafe URLs (javascript:…) and attributes.
      .use(rehypeSanitize)
      // After sanitizing, which would otherwise strip Shiki's colours.
      .use(rehypeShikiFromHighlighter, highlighter, {
        ...SHIKI_OPTIONS,
        defaultLanguage: 'text',
        fallbackLanguage: 'text',
      })
      .use(rehypeStringify)
  )
}

Sûr par défaut

Le contenu vient d'un CMS que je contrôle : pourquoi le nettoyer ? Parce que « je le contrôle » reste vrai jusqu'au jour où ça ne l'est plus : un extrait copié-collé, un compte compromis, un auteur invité un jour. Deux couches protègent chaque page :

  • Le HTML brut présent dans le Markdown est supprimé à la conversion, jamais transmis.
  • rehype-sanitize retire ensuite tout ce qui reste de dangereux, comme les liens javascript:.

Du code clair et sombre sans nouveau rendu

Shiki peut écrire d'un coup les couleurs claire et sombre de chaque élément dans le HTML, sous forme de variables CSS. La feuille de style choisit l'une des deux selon le thème actif : le code change de couleur à l'instant où le thème change, sans nouvelle requête, sans nouveau rendu, sans clignotement.

lib/markdown/render.server.ts
// Light and dark colours both go into the HTML as CSS variables; app.css picks
// one from the site theme, so highlighted code switches theme with no re-render.
const SHIKI_OPTIONS = {
  themes: { light: 'github-light', dark: 'github-dark' },
  defaultColor: false,
} as const
styles/app.css
.shiki,
.shiki span {
  color: var(--shiki-light);
}
.dark .shiki,
.dark .shiki span {
  color: var(--shiki-dark);
}

Créer une seule fois ce qui coûte cher

Charger les grammaires de Shiki prend du temps : le coloriseur et la chaîne de traitement sont donc créés une fois par serveur, puis partagés par toutes les requêtes suivantes.

lib/markdown/render.server.ts
// Loading grammars is slow, so the highlighter and processor are created once per server.
let highlighter: Promise<Highlighter> | undefined

function getHighlighter() {
  highlighter ??= createHighlighter({
    themes: Object.values(SHIKI_OPTIONS.themes),
    langs: LANGUAGES,
  })
  return highlighter
}

C'est tout pour cette série, pour l'instant. Un dernier détail : cet article est passé par la même chaîne, et le code ci-dessus a donc été coloré par le code ci-dessus.

Newsletter

Recevez les nouveaux articles par e-mail

Un e-mail par nouvel article. Désabonnement à tout moment.

Vous préférez un lecteur de flux ? Flux RSS

Réponses

Jamais affiché sur le site.

Les réponses apparaissent une fois approuvées.

Contact

Un projet en tête ?
Construisons-le.

Ouvert aux postes en télétravail et aux missions freelance.