Personnalisation de React-Markdown en Typescript

Personnalisation de React-Markdown en Typescript
Alexandre P. dans Dev - mis à jour le 03-05-2025

React-Markdown est une librairie d'interprétation de contenu markdown qui permet de générer automatiquement le code HTML. Cependant, il génère des balises standards, sans classes et les affiche. Si vous voulez personnaliser tout ça, il faudra créer votre CSS en fonction de l'élément parent. Nous allons voir comment créer nos propres renderer pour les composants HTML avec react-markdown en typescript.

Une meilleure association Strapi Next.js

Lorsque vous rédigez vos articles avec Strapi, un input de type RichText se contentera de générer du texte au format Markdown. Pour pouvoir l'afficher avec la mise en forme, côté Next.js, nous allons utiliser un composant React : React-Markdown.

A quoi sert cette librairie ?

Elle remplace le Markdown généré par Strapi en composants HTML plus traditionnel.

Des

<H1> <H2> <H3>

pour les titres,

des

<A> 

pour les liens,

des

<UL>

pour les listes...

Cette librairie est personnalisable à souhait. On peut remplacer pour chaque élément HTML rendu par un composant custom et la documentation nous montre comment nous y prendre :

import React from 'react'
import ReactDOM from 'react-dom'
import ReactMarkdown from 'react-markdown'
import MyFancyRule from './components/my-fancy-rule.js'

ReactDOM.render(
  <ReactMarkdown
    components={{
      // Use h2s instead of h1s
      h1: 'h2',
      // Use a component instead of hrs
      hr: ({node, ...props}) => <MyFancyRule {...props} />
    }}
  >
    # Your markdown here
  </ReactMarkdown>,
  document.querySelector('#content')
)

En JSX cela ne pose aucun problème, mais en TSX c'est une autre pair de manches...

Comment se fait la surcharge de React-Markdown

On passe un objet à la propriété components, et chaque clé de cet objet représente un composant. Le problème, c'est que le renderer est une fonction et cela pose pas mal de problème pour pouvoir typer ces éléments.

Voici comment j'ai procédé :

Je commence par créer mon composant MarkdownLink qui retourne un JSX.Element basique :

import * as React from 'react'

type MarkdownLinkProps = {
  href: string
  children: React.ReactNode
}

const MarkdownLink = ({ href, children }: MarkdownLinkProps) => {
  return (
    <a href={href} target="_blank" rel="noreferrer">
      {children}
    </a>
  )
}

export default MarkdownLink

Puis au moment du render de React-Markdown :

<ReactMarkdown
    components={{
        a: ({ node, children }) => {
           return (
               <MarkdownLink href={(node.properties?.href as string) ?? ''}>
                  {children[0]}
               </MarkdownLink>
            )
        },
    }}
   >
   {post.attributes.content}
</ReactMarkdown>

Et voilà, notre composant remplace désormais la balise standard généré par React-Markdown. 😉

FAQ

Pourquoi utiliser React-Markdown plutôt que d'afficher le Markdown brut directement ?

Le contenu Markdown brut n'est pas interprété par le navigateur, il s'affiche tel quel sans mise en forme. React-Markdown convertit automatiquement ce texte en balises HTML lisibles et structurées.

Pourquoi la surcharge des composants pose-t-elle problème en TypeScript ?

En JSX, le typage est souple et les fonctions de rendu passées à la prop components sont acceptées sans friction. En TypeScript, le compilateur exige un typage précis des paramètres de ces fonctions, ce qui nécessite une approche plus explicite.

Comment récupérer l'attribut href d'un lien dans un renderer personnalisé ?

Dans la fonction de rendu, on accède à node.properties?.href qui provient de l'arbre syntaxique interne. Un cast en string avec une valeur par défaut permet d'éviter les erreurs de typage.

Est-ce qu'on peut personnaliser d'autres balises que les liens avec cette approche ?

Oui, la prop components accepte une clé pour chaque élément HTML que React-Markdown peut générer, comme h1, ul, hr ou img. Il suffit de répéter le même principe pour chaque balise à surcharger.

Faut-il obligatoirement créer un composant séparé pour chaque élément personnalisé ?

Non, on peut écrire la logique directement dans la fonction inline passée à components. Créer un composant séparé est surtout utile quand le rendu est complexe ou réutilisé ailleurs dans le projet.

#react#react-markdown#typescript#conseils#programming

user picture

Alexandre P.

Développeur passionné depuis plus de 20 ans, j'ai une appétence particulière pour les défis techniques et changer de technologie ne me fait pas froid aux yeux.