<!--
  canonical-url: https://www.avonture.be/fr/blog/docusaurus-override-img
  published:     2025-08-21
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Modifier la façon dont Docusaurus génère les balises img

> Personnalisez la manière dont Docusaurus génère les balises <img> pour mieux contrôler le rendu et le comportement des images.

---
![Modifier la façon dont Docusaurus génère les balises img](/img/v2/docusaurus_component.webp)

**TL;DR**

Cet article explique comment personnaliser le rendu des images dans Docusaurus en créant un composant React `<Image>` sur mesure. Vous allez apprendre à construire le composant, à le styler en CSS, puis à utiliser un plugin Remark personnalisé pour remplacer automatiquement les images Markdown standard par votre nouveau composant. Le guide couvre aussi la configuration du plugin pour ignorer la première image d'un article et son enregistrement dans la configuration de Docusaurus, ce qui vous donne un contrôle total sur l'affichage de vos images.

Je démarre une série sur l'écriture de composants pour Docusaurus.

Comme c'est toujours plus sympa de commencer par quelque chose de très concret et pratique, voici ce que nous allons faire : nous allons intercepter la conversion de `![Alt text](./img/example.webp)` vers une balise HTML `<img>`. Nous allons forcer Docusaurus à utiliser à la place notre propre composant `<Image>`.

Nous allons personnaliser notre balise `<Image>` pour d'abord injecter un `<div>` parent, puis forcer notre `<img>` avec des attributs comme le CSS, le lazy loading, ... **mais pas pour toutes les balises de l'article, puisque nous allons ignorer la première.**

En effet, l'image d'introduction de l'article doit rester inchangée ; nous commencerons à modifier les autres images.

Et voici le résultat de notre composant `<Image>` fait main :

(image: Un suricate heureux)

Comme vous le voyez, l'image est centrée, elle a un effet au survol, des coins arrondis, ... Si vous regardez le code HTML, vous verrez l'attribut `loading="lazy"`.

C'est plutôt chouette : notre composant fonctionne ! Construisons-le.

Il y a plusieurs choses à faire, alors commençons par la première : créer notre composant `<Image>`.

## 1. Création de notre composant Image {#1-creation-of-our-image-component}

Écrire un composant pour Docusaurus est assez simple. Il faut créer un nouveau dossier dans le dossier existant `src/components`.

Dans cet article, nous allons créer un composant `Image` ; créons donc le fichier `src/components/Image/index.tsx` avec ce contenu :

**src/components/Image/index.tsx**

```typescript
/**
 * 🖼️ Image Component
 *
 * A lightweight wrapper for rendering images in Docusaurus with base URL resolution.
 * Ensures that image paths are correctly resolved relative to the site's base URL,
 * making it ideal for static assets stored in the `static/img` directory.
 *
 * 🔍 Behavior:
 * - Uses `useBaseUrl()` to resolve the image path
 * - Applies scoped styling via CSS modules
 * - Supports optional `alt` and `title` attributes for accessibility and tooltips
 * - Enables lazy loading for performance optimization
 */

import type { JSX } from "react";
import styles from "./styles.module.css";
import useBaseUrl from "@docusaurus/useBaseUrl";

interface Props {
  /** Path to the image (e.g. `/img/example.png`), or a `require()`d asset */
  src: string | { default: string };
  /** Tooltip text shown on hover */
  title?: string;
  /** Alternative text for accessibility (if missing, reuse the Title property) */
  alt?: string;
}

export default function Image({ src, alt, title }: Props): JSX.Element {
  const isAbsolutePath = typeof src === "string" && src.startsWith("/");
  const resolvedUrl = useBaseUrl(isAbsolutePath ? src : undefined);
  const imgSrc = (isAbsolutePath ? resolvedUrl : src) as string;

  return (
    <div className={styles.container}>
      <img src={imgSrc} alt={alt || title} title={title} loading="lazy" />
    </div>
  );
}
```

Notre composant `Image` attend trois paramètres, mais seul `img` est obligatoire. On peut aussi passer un titre (`title`) et le texte alternatif (`alt`).

Comme vous le comprenez facilement, le composant `<Image>` va simplement créer un `div` contenant une balise `img`.

Rien d'extraordinaire, sauf que nous utiliserons un peu de CSS pour le look & feel et que nous forcerons le [lazy loading](/blog/docusaurus-lazy-loading).

Créez également le fichier `src/components/Image/styles.module.css` :

**src/components/Image/styles.module.css**

```css
.container {
  text-align: center;
  padding: 10px;
  background: rgb(255, 255, 255, 0.15);
  border: 1px solid rgb(255, 255, 255, 0.3);
  border-radius: 16px;
  backdrop-filter: blur(10px) saturate(180%);
}

.container img {
  border-radius: 12px;
  box-shadow: 0 8px 20px rgb(0, 0, 0, 0.15);
  transition:
    transform 0.4s ease,
    box-shadow 0.4s ease,
    filter 0.4s ease;
}

.container img:hover {
  transform: scale(1.05) translateY(-4px);
  box-shadow: 0 12px 25px rgb(0, 0, 0, 0.25);
  filter: brightness(1.05) saturate(1.1);
}
```

## 2. Informer Docusaurus de l'existence de notre composant {#2-tell-docusaurus-about-our-component}

Avant de pouvoir utiliser le composant `<Image>`, il faut dire à Docusaurus quoi faire quand il le rencontrera.

Éditez le fichier `src/theme/MDXComponents.js` (et s'il n'existe pas, créez-le)

**src/theme/MDXComponents.js**

```javascript
import MDXComponents from "@theme-original/MDXComponents";
import Image from "@site/src/components/Image";

export default {
  // Reusing the default mapping
  ...MDXComponents,
  Image,
};
```

Tout est maintenant en place.

## 3. Utiliser le composant Image {#3-using-the-image-component}

Dès maintenant, dans un article de blog, au lieu d'écrire

```html
![A happy meerkat](./images/happy.webp)
```

nous pouvons écrire

```html
<Image src={require("./images/happy.webp").default} title="A happy meerkat" />
```

La partie `require` est nécessaire parce que l'image se trouve dans un sous-dossier de l'article. C'est exactement la balise rendue en haut de cet article.

Si la source était quelque chose comme `/img/happy.jpg`, le code est plus simple :

```html
<Image src="/img/happy.jpg" title="A happy meerkat" />
```

> **Caution:**
>
> Mais, oh oh, sur ce blog, j'ai plus de 250 articles à l'heure actuelle (août 2025) ; je ne vais quand même pas parcourir tous mes articles pour remplacer mes images, si ?
>
> Et puis, peut-être que demain je ne voudrai plus de cette balise ; je veux donc garder mes articles en **Markdown vanilla** (le langage standard). Comment faire ? La réponse : écrire un plugin (j'ai utilisé exactement la même astuce pour mon [plugin de recherche & remplacement](/blog/docusaurus-plugin-replace)).

Le plugin va pouvoir intercepter le code Markdown de l'article et le manipuler. C'est exactement ce que nous voulons ici !

Nous allons intercepter la conversion des images de Markdown vers HTML et dire à Docusaurus d'utiliser notre balise Image au lieu de img.

## 4. Écrire et enregistrer un plugin (optionnel — seulement si vous avez des centaines d'articles comme moi) {#4-writing-and-registering-a-plugin-optional--only-if-you-have-hundreds-of-articles-like-me}

Si vous n'avez qu'une poignée d'articles, il est plus simple de les éditer à la main et de sauter cette section. Nous avons donc besoin d'un plugin pour intercepter la conversion de `![Alt text](./img/example.webp)` vers une balise image HTML ; automatiquement.

> **Caution:**
>
> Ajoutons une complexité : nous ne voulons pas modifier la première image, c'est-à-dire l'image d'introduction de l'article. Pour cette première image, laissons Docusaurus faire son travail (il le fait très bien). Nous intercepterons à partir de la deuxième image de l'article.

Créez le fichier `plugins/remark-image-transformer/index.cjs` :

**plugins/remark-image-transformer/index.cjs**

```cjs
/**
 * @fileoverview
 * A Remark plugin for transforming Markdown image nodes (`![alt](url)`) into MDX-compatible `<Image />` components.
 * This plugin is tailored for Docusaurus environments, where image paths often need to be resolved using `require()` or `useBaseUrl()`.
 * It supports:
 * - Relative paths (e.g. `./image.png`) → converted to `require()` expressions
 * - Absolute paths (e.g. `/img/banner.png`) → passed directly, as Docusaurus resolves them
 * - External URLs → passed directly as string literals
 * Additionally, it preserves `alt` and `title` attributes.
 *
 * @module remarkReplaceImgToImage
 */

// cspell:ignore mdxjs

const { visit } = require("unist-util-visit");
const acorn = require("acorn");

/**
 * Parses a JavaScript expression string into an ESTree-compatible expression node.
 *
 * @param {string} expression - A valid JavaScript expression as a string.
 * @returns {object} ESTree expression node representing the parsed expression.
 */
function expressionToEstree(expression) {
  return acorn.parse(expression, {
    ecmaVersion: 2020,
    sourceType: "module",
  }).body[0].expression;
}

/**
 * Creates an MDX JSX attribute node with an embedded JavaScript expression.
 *
 * @param {string} name - The name of the JSX attribute (e.g. "src").
 * @param {string} expr - A JavaScript expression string to embed in the attribute.
 * @returns {object} MDX AST node representing the JSX attribute with expression value.
 */
function makeJsxAttributeExpression(name, expr) {
  return {
    type: "mdxJsxAttribute",
    name,
    value: {
      type: "mdxJsxAttributeValueExpression",
      value: expr,
      data: {
        estree: {
          type: "Program",
          body: [
            {
              type: "ExpressionStatement",
              expression: expressionToEstree(expr),
            },
          ],
          sourceType: "module",
        },
      },
    },
  };
}

/**
 * Remark plugin that replaces Markdown image nodes with MDX `<Image />` components.
 *
 * @param {object} [options={}] - Plugin configuration options.
 * @param {boolean} [options.skipFirst=true] - Whether to skip transforming the first image node.
 * @returns {function} Transformer function to be used by Remark.
 */
function remarkReplaceImgToImage({ skipFirst = true } = {}) {
  return (tree) => {
    let imageCount = 0;

    visit(tree, "image", (node, index, parent) => {
      imageCount++;
      if (skipFirst && imageCount === 1) return;

      const url = node.url || "";
      const attributes = [
        { type: "mdxJsxAttribute", name: "alt", value: node.alt || "" },
      ];
      if (node.title)
        attributes.push({
          type: "mdxJsxAttribute",
          name: "title",
          value: node.title,
        });

      if (/^\.+\//.test(url)) {
        // Relative to MD file -> use require()
        const expr = `require("${url}").default`;
        attributes.unshift(makeJsxAttributeExpression("src", expr));
      } else if (/^\//.test(url)) {
        // Absolute -> just pass the path, Docusaurus will handle it.
        attributes.unshift({
          type: "mdxJsxAttribute",
          name: "src",
          value: url,
        });
      } else {
        // Fallback: external URL
        attributes.unshift({
          type: "mdxJsxAttribute",
          name: "src",
          value: url,
        });
      }

      const imageNode = {
        type: "mdxJsxFlowElement",
        name: "Image",
        attributes,
        children: [],
      };

      parent.children.splice(index, 1, imageNode);
    });

    // The useBaseUrl import is no longer needed.
  };
}

module.exports = remarkReplaceImgToImage;
```

*Ce plugin est plus complexe parce qu'il faut s'assurer que les chemins vers les images sont correctement gérés (on utilise des chemins relatifs comme `![Example image](./img/example.webp)`, ou absolus (`![Example image](/img/example.webp)`), ou même externes).*

Une fois le plugin créé, il faut mettre à jour la configuration de Docusaurus pour le charger.

Pour cela, éditez votre fichier `docusaurus.config.js` et ajoutez les lignes mises en évidence comme illustré ci-dessous.

**docusaurus.config.js**

```javascript
const config = {
  // [ ... ]
  presets: [
    [
      'classic',
      ({
        blog: {
          // highlight-next-line
          beforeDefaultRemarkPlugins: [require('./plugins/remark-image-transformer')],
        },
        // [ ... ]
      }),
    ],
  ],
};

export default config;
```

Redémarrez maintenant votre serveur Docusaurus (pour que le plugin soit enregistré).

Si tout est correctement en place, ouvrez n'importe lequel de vos anciens articles et vous verrez que ça fonctionne.

## Conclusion {#conclusion}

À vous maintenant d'éditer le fichier `src/components/Image/styles.module.css` et d'utiliser votre propre CSS.

Et une fois que chaque image passe par votre propre composant, il devient facile de les auditer : voyez [Running some checks on your Docusaurus images](/blog/docusaurus-check-images).
