<!--
  canonical-url: https://www.avonture.be/fr/blog/gemini-tldr
  published:     2026-02-16
  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.
-->

# Automatiser les résumés TL;DR avec Gemini AI

> Améliorez l'expérience de vos lecteurs en générant automatiquement des résumés TL;DR concis pour vos articles de blog avec Gemini AI de Google et Python.

---
![Automatiser les résumés TL;DR avec Gemini AI](/img/v2/gemini_tldr.webp)

**TL;DR**

Écrire des résumés pour de longs articles améliore l'expérience utilisateur, mais ça prend du temps. Cet article explique comment automatiser la génération de sections « Too Long; Didn't Read » (TL;DR) avec un script Python et Gemini AI de Google. Nous verrons la mise en place Docker, l'utilisation du script et son intégration dans votre workflow Docusaurus.

En tant que blogueur technique, je cherchais un moyen d'améliorer l'expérience de mes lecteurs en proposant des résumés concis au début de mes articles — pour qu'ils ne perdent pas leur temps (j'espère !) à lire un article entier avant de se dire : « Attends, ce n'est pas ce que je cherchais. »

J'ai donc décidé d'automatiser la génération de résumés TL;DR avec Gemini AI de Google et un simple script Python. Dans cet article, je vous explique comment j'ai fait.

*Ce n'est pas mon seul usage de Gemini sur ce blog : lisez [How I used Google Gemini Nano Banana on my blog](/blog/gemini-meerkat) pour le volet illustrations de l'histoire.*

**Quick Jump:** [Avant et après](#before-and-after) | [Récupérer tous les fichiers](#conclusion)

## Avant et après {#before-and-after}

Le script prend un article entier — chaque paragraphe, chaque titre — et le réduit à quelques phrases. Voici un exemple réel, tiré directement de [l'article sur Lovable.dev](/blog/lovable-dev-ai) de ce blog.

**Avant** — les premiers paragraphes que le script lit réellement :

> Cette semaine, un collègue m'a parlé de Lovable.dev en me disant : « *Dans un prompt, il suffit de décrire le programme que vous voulez générer, et l'outil le construit et le déploie même pour vous* ». Waouh, il fallait absolument que j'essaie.
>
> Mais que demander ? Et si je lui demandais de créer un clone de Marknotes ? Ceux qui me suivent depuis des années savent que j'ai créé Marknotes, une application de prise de notes, il y a 10 ans. J'y ai travaillé pendant cinq ans avant de passer à autre chose.
>
> Voyons si Lovable.dev peut construire la même chose en une seule heure. **Spoiler : non, mais ce n'était pas si mal.**

**Après** — le `<TLDR>` que le script a généré et injecté en haut de ce même article :

![Le TLDR généré, tel qu'il s'affiche sur l'article publié](./images/example_tldr_output.png)

La même densité d'information dont les lecteurs ont vraiment besoin, en une fraction du temps de lecture.

## Le concept {#the-concept}

L'idée est simple : il me faut un script qui parcourt mes articles de blog (un ou plusieurs) et génère un court résumé avec Gemini AI. Ensuite, il doit insérer ce résumé dans l'article, enveloppé dans un composant `<TLDR>` maison pour le style et la visibilité.

Le script va faire ceci :

1. Recevoir un chemin en entrée (fichier ou dossier).
2. Lire les fichiers Markdown.
3. Envoyer le contenu à Gemini AI pour résumé.
4. Récupérer le résumé.
5. Réinjecter le résumé dans le fichier Markdown.

Ma structure de dossiers ressemble à ceci : `blog/YYYY/MM/DD/index.md` pour chaque article. Donc en appelant le script avec `blog/2026/01/`, il traitera tous les articles de janvier 2026.

## Le composant React {#the-react-component}

D'abord, il nous faut une façon d'afficher joliment le résumé dans Docusaurus. J'utilise un simple composant maison pour ça. Voici l'implémentation :

```html
<TLDR> This is the summary of the article... </TLDR>
```

Le code du composant est le suivant :

**src/components/TLDR/index.tsx**

```typescript
import { useMemo, type JSX, type ReactNode } from "react";
import Head from "@docusaurus/Head";
import MobileQuickLinks from "@site/src/components/Blog/MobileQuickLinks";
import styles from "./styles.module.css";
import { translate } from "@docusaurus/Translate";

// Recursively extracts plain text from React nodes for JSON-LD abstract
function extractText(node: unknown): string {
  if (typeof node === "string") return node;
  if (typeof node === "number") return String(node);
  if (Array.isArray(node)) return node.map(extractText).join("");
  if (node && typeof node === "object") {
    const children = (node as { props?: { children?: unknown } }).props?.children;
    if (children) return extractText(children);
  }
  return "";
}

interface Props {
  children: ReactNode;
}

// Injects JSON-LD so search engines read the abstract via Schema.org BlogPosting
export default function TLDR({ children }: Props): JSX.Element {
  const structuredData = useMemo(
    () => ({
      "@context": "https://schema.org",
      "@type": "BlogPosting",
      abstract: extractText(children),
    }),
    [children],
  );

  return (
    <>
      <Head>
        <script type="application/ld+json">{JSON.stringify(structuredData)}</script>
      </Head>

      <div
        className={styles.tldrContainer}
        role="region"
        aria-label={translate({ id: "tldr.ariaLabel", message: "Article Summary" })}
      >
        <span
          role="img"
          aria-label={translate({ id: "tldr.iconLabel", message: "lightning" })}
          className={styles.tldrIcon}
        >
          ⚡
        </span>

        <div className={styles.tldrBody}>
          <strong className={styles.tldrTitle}>TL;DR</strong>
          <div className={styles.tldrContent}>{children}</div>
        </div>
      </div>

      {/*
        The TL;DR is the one block every post opens with, which makes it the only reliable
        anchor for a "read next" hint placed *after* the reader has confirmed the article is
        the right one. MobileQuickLinks decides on its own whether it has anything to show.
      */}
      <MobileQuickLinks />
    </>
  );
}
```

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

```css
/*
 * TL;DR — was borrowing Infima's "alert alert--info" utility classes, which is
 * where its unexplained Docusaurus blue came from. The summary is the first
 * block of every article on the site: it should carry the site's own colour,
 * not a framework default.
 */

.tldrContainer {
  composes: mk-callout from global;
  composes: mk-callout--key from global;
}

/*
 * font-variant-emoji: text asks for the monochrome TEXT presentation of the
 * lightning character, so it takes --mk-on-line from .mk-dot like every other
 * dot glyph in the site. Without it the emoji keeps its own yellow-blue
 * colours on the terracotta disc — the one bicolour pastille on the page.
 * index.tsx needs no change.
 */
.tldrIcon {
  composes: mk-dot from global;
  font-size: 0.9rem;
  font-variant-emoji: text;
}

.tldrBody {
  min-width: 0;
}

.tldrTitle {
  composes: mk-eyebrow from global;
  display: block;
  margin-bottom: 0.25rem;
  color: inherit;
}

/*
 * The old rule indented the summary by a hardcoded 2.5rem to clear the icon,
 * and dimmed it with opacity: 0.9. Both are gone: the flex row aligns the text
 * for free, and a summary is the last thing on the page that should be faded.
 */
.tldrContent {
  font-size: 0.95rem;
  line-height: 1.65;
}

.tldrContent > :last-child {
  margin-bottom: 0;
}

.tldrContent a {
  color: var(--tone-line);
  text-decoration: underline;
  text-underline-offset: 2px;
}
```

## Le script d'automatisation {#the-automation-script}

Sur mon blog, j'ai créé un dossier `.scripts/python_tldr` pour héberger le script Python et ses dépendances.

> **API Gemini:**
>
> Il vous faudra une clé d'API Google Gemini pour que ça fonctionne. Vous pouvez en obtenir une sur <https://aistudio.google.com/api-keys>.

### Exécution avec Docker {#running-with-docker}

Comme je suis un grand fan de Docker, je voulais m'assurer que le script tourne dans un environnement reproductible.

Voici comment démarrer le container :

```bash
$ docker run -it --rm --env-file .env -v .:/app -w /app python sh -c "pip install google-genai python-dotenv > /dev/null 2>&1 && /bin/bash"
```

Cette commande va créer un container Python, monter votre répertoire courant sur `/app`, lire et charger votre fichier d'environnement, installer les packages nécessaires (`google-genai` pour l'accès à l'API Gemini et `python-dotenv` pour la gestion des variables d'environnement), puis lancer un shell bash où vous pourrez exécuter le script.

> **Variables d'environnement:**
>
> Vous devrez créer un fichier `.env` à la racine de votre projet avec le contenu suivant :
>
> **.env**
>
> ```
> GOOGLE_API_KEY=your_gemini_api_key_here
> ```

### Générer les résumés {#generating-summaries}

Une fois dans le container, vous pouvez lancer le script sur un fichier précis ou sur tout un répertoire.

Pour traiter un seul fichier :

```bash
$ python .scripts/python_tldr/main.py blog/2026/01/index.md
```

Pour traiter tous les articles d'un mois donné :

```bash
$ python .scripts/python_tldr/main.py blog/2026/01/
```

Le script est assez malin pour ignorer les fichiers qui contiennent déjà un tag `<TLDR>`, vous pouvez donc le lancer sans risque sur toute l'archive de votre blog.

> **Soyez transparent avec vos lecteurs:**
>
> Un résumé écrit par une machine reste du contenu généré par une machine. Je le signale explicitement sur chaque article concerné ; voir [How to indicate AI-assisted content in a Docusaurus blog](/blog/docusaurus-ai-gemini).

Le même principe — *générer une fois à l'écriture, committer le résultat* — est réutilisé dans [AI-Powered Code Tooltips in Docusaurus](/blog/docusaurus-eli5-snippet-tooltips) : les lecteurs n'attendent jamais un appel d'API et votre clé ne quitte jamais votre machine.

## Erreur - 429 RESOURCE\_EXHAUSTED {#error---429-resource\_exhausted}

Cette erreur indique que vous avez dépassé vos limites d'utilisation de l'API Gemini. Selon votre plan, il peut y avoir des restrictions sur le nombre de requêtes ou sur le volume de données traitables dans un intervalle de temps donné.

*Le plan gratuit est assez limité : si vous avez beaucoup d'articles, envisagez de passer à un plan supérieur ou de lancer le script par lots pour éviter d'atteindre la limite.*

Vous pouvez toujours utiliser la même fonctionnalité, mais manuellement : allez sur <https://gemini.google.com/app>, collez le contenu de votre article et demandez à Gemini de générer un résumé TL;DR. Copiez ensuite le résultat et collez-le dans votre article, à l'intérieur du composant `<TLDR>`.

Voici le prompt utilisé pour obtenir le résumé :

```text
You are an expert technical editor.

Read the following article content and generate a 'TL;DR' summary.

The summary must be concise (max 3 sentences) and written in the same language as the article.
```

## Conclusion {#conclusion}

Vous trouverez ci-dessous tous les fichiers nécessaires pour mettre ça en place sur votre propre blog Docusaurus. N'hésitez pas à adapter et améliorer le script selon vos besoins !

**Project: /your\_docusaurus\_site**

> **Note:**
>
> Maintenant, lancez 'docker run -it --rm -v .:/app -w /app python sh -c "pip install google-genai python-dotenv && python .scripts/python\_tldr/main.py blog/"' pour ajouter le résumé TLDR dans chaque article sous /blog/.

**.env**

```
GOOGLE_API_KEY=your_gemini_api_key_here
```

**.scripts/python\_tldr/main.py**

```python
# How to run:
#
# 1. Ensure you have Docker installed.
# 2. Place your Gemini API key in a .env file at the root of your blog:
#    GEMINI_API_KEY=your_actual_api_key_here
# 3. Start a terminal, go to your blog and run the following command,
#    docker run -it --rm -v ${PWD}:/app -w /app python sh -c "pip install google-genai python-dotenv  > /dev/null 2>&1 && /bin/bash"
# 4. Inside the container, run:
#    python .scripts/python_tldr/main.py <path-to-markdown-file-or-directory>
#
# Examples:
#    Process the entire blog directory:
#       python .scripts/python_tldr/main.py blog/
#
#    Process a specific year i.e. all posts from 2026:
#       python .scripts/python_tldr/main.py blog/2026/
#
#    Process a specific year and month i.e. all posts from January 2026:
#       python .scripts/python_tldr/main.py blog/2026/01
#
#    Process a single blog post:
#       python .scripts/python_tldr/main.py blog/2026/01/05

import argparse
import os
import sys
from pathlib import Path
from typing import List, Final
from dotenv import load_dotenv

# --- PATH PATCH ---
# Ensures 'src' module is found regardless of execution context
BASE_DIR = Path(__file__).resolve().parent
if str(BASE_DIR) not in sys.path:
    sys.path.insert(0, str(BASE_DIR))
# ------------------

try:
    from src.ai_service import AIService
    from src.file_manager import FileManager
except ImportError as e:
    print(f"❌ Configuration Error: Could not import internal modules.\nDetails: {e}")
    sys.exit(1)

def process_single_file(file_path: Path, ai_service: AIService) -> str:
    """
    Processes a single markdown file: reads, checks for existence, generates TL;DR, and injects.
    Returns a status string for logging.
    """
    try:
        file_manager = FileManager(file_path)
        content = file_manager.read_content()

        # 1. Skip if already exists
        if file_manager.has_tldr(content):
            return "SKIPPED (Exists)"

        # 2. Generate
        print(f"   Generating summary for {file_path.name}...")
        tldr = ai_service.generate_tldr(content)

        # 3. Inject
        file_manager.inject_tldr(content, tldr)
        return "SUCCESS"

    except Exception as e:
        return f"ERROR: {e}"

def main() -> None:
    """Main execution entry point."""
    load_dotenv(dotenv_path=BASE_DIR / ".env")
    if not os.getenv("GEMINI_API_KEY"):
        load_dotenv() # Fallback

    parser = argparse.ArgumentParser(description="Inject AI-generated TL;DR into markdown files recursively.")
    parser.add_argument("path", type=Path, help="Path to a markdown file or a directory containing markdown files.")
    args = parser.parse_args()

    target_path: Path = args.path
    if not target_path.is_absolute():
        target_path = Path.cwd() / target_path

    if not target_path.exists():
        print(f"❌ Error: Path not found: {target_path}")
        sys.exit(1)

    # Validate API Key
    api_key: str | None = os.getenv("GEMINI_API_KEY")
    if not api_key:
        print("❌ Error: GEMINI_API_KEY not found.")
        sys.exit(1)

    # Initialize AI Service once (reused for all files)
    try:
        ai_service = AIService(api_key)
    except Exception as e:
        print(f"❌ Error initializing AI Service: {e}")
        sys.exit(1)

    # Collect files to process
    files_to_process: List[Path] = []

    if target_path.is_file():
        if target_path.suffix.lower() == ".md":
            files_to_process.append(target_path)
    elif target_path.is_dir():
        # Recursive search for all .md files
        print(f"📂 Scanning directory: {target_path} ...")
        files_to_process = list(target_path.rglob("*.md"))

    if not files_to_process:
        print("⚠️  No Markdown files found to process.")
        sys.exit(0)

    print(f"🔍 Found {len(files_to_process)} file(s). Starting processing...\n")

    # Stats counters
    stats = {"SUCCESS": 0, "SKIPPED": 0, "ERROR": 0}

    for i, file_path in enumerate(files_to_process):
        print(f"📄 Processing [{i+1}/{len(files_to_process)}]: {file_path.name}...", end="", flush=True)

        status = process_single_file(file_path, ai_service)

        if "SKIPPED" in status:
            stats["SKIPPED"] += 1
            print(f"\r⚠️  {file_path.name}: {status}")
            # Pas besoin de dormir si on a sauté le fichier
        elif "ERROR" in status:
            stats["ERROR"] += 1
            print(f"\r❌ {file_path.name}: {status}")
            # En cas d'erreur 429, on pourrait vouloir attendre plus,
            # mais ici on continue simplement.
        else:
            stats["SUCCESS"] += 1
            print(f"\r✅ {file_path.name}: Injected successfully.")

    # Final Summary
    print("\n" + "="*30)
    print("       BATCH COMPLETE")
    print("="*30)
    print(f"✅ Injected: {stats['SUCCESS']}")
    print(f"⏭️  Skipped:  {stats['SKIPPED']}")
    print(f"❌ Failed:   {stats['ERROR']}")
    print("="*30)

if __name__ == "__main__":
    main()
```

**.scripts/python\_tldr/src/ai\_service.py**

```python
import os
import time
import logging
from typing import Final
from google import genai
from google.genai import types
from google.genai.errors import ClientError

class AIService:
    """
    Handles interactions with the Google Gen AI SDK (v1.0+).
    Includes retry logic for rate limits (429).
    """

    # Let's switch to the standard Flash 2.0.
    # It often has a more stable pool than the 'Lite' or 'Preview' versions.
    _MODEL_NAME: Final[str] = "gemini-2.0-flash"

    def __init__(self, api_key: str | None) -> None:
        if not api_key:
            raise ValueError("API Key is missing. Check your .env file.")

        self._client = genai.Client(api_key=api_key)

    def generate_tldr(self, content: str) -> str:
        """
        Generates a concise TL;DR summary using Gemini.
        Includes exponential backoff for 429 Resource Exhausted errors.
        """
        prompt: str = (
            "You are an expert technical editor. "
            "Read the following article content and generate a 'TL;DR' summary. "
            "The summary must be concise (max 3 sentences) and written in the same language as the article. "
            "Do NOT use blockquotes (>), markdown formatting, or introductory text. "
            "Just provide the raw summary text.\n\n"
            f"Content:\n{content}"
        )

        max_retries = 5
        base_delay = 5  # Start with 5 seconds

        for attempt in range(max_retries):
            try:
                response = self._client.models.generate_content(
                    model=self._MODEL_NAME,
                    contents=prompt,
                    config=types.GenerateContentConfig(
                        temperature=0.3,
                    )
                )

                if not response.text:
                    raise RuntimeError("Empty response from Gemini API.")

                return response.text.strip().lstrip(">").strip()

            except ClientError as e:
                # Check if it is a Quota/Rate Limit error (429)
                if e.code == 429:
                    if attempt == max_retries - 1:
                        raise RuntimeError(f"Max retries exceeded after 429 error: {e}") from e

                    # Calculate wait time: 5s, 10s, 20s, 40s...
                    wait_time = base_delay * (2 ** attempt)
                    print(f"\n   ⏳ Quota hit (429). Waiting {wait_time}s before retry {attempt + 1}/{max_retries}...")
                    time.sleep(wait_time)
                else:
                    # If it's another error (400, 404, 500), crash immediately
                    raise RuntimeError(f"AI Generation failed: {e}") from e

            except Exception as e:
                raise RuntimeError(f"Unexpected error: {e}") from e

        raise RuntimeError("Failed to generate TL;DR after retries.")
```

**.scripts/python\_tldr/src/file\_manager.py**

```python
from pathlib import Path
from typing import Final, List

class FileManager:
    """
    Handles file reading, content checking, and safe injection.
    """

    _FRONTMATTER_DELIMITER: Final[str] = "---"
    _IMAGE_START_TAG: Final[str] = "!["
    _TLDR_TAG: Final[str] = "<TLDR>"

    def __init__(self, file_path: Path) -> None:
        if not file_path.exists():
            raise FileNotFoundError(f"File not found: {file_path}")
        self._file_path = file_path

    def read_content(self) -> str:
        """Reads the file content securely."""
        return self._file_path.read_text(encoding="utf-8")

    def has_tldr(self, content: str) -> bool:
        """
        Checks if the TLDR component is already present in the content.
        """
        return self._TLDR_TAG in content

    def inject_tldr(self, original_content: str, tldr_text: str) -> None:
        """
        Injects the TL;DR into the content using the custom <TLDR> React component.
        Inserts after the first image following the frontmatter.
        """
        lines: List[str] = original_content.splitlines()
        insertion_index: int = 0

        # 1. Detect Frontmatter end
        if lines and lines[0].strip() == self._FRONTMATTER_DELIMITER:
            try:
                frontmatter_end_index = lines.index(self._FRONTMATTER_DELIMITER, 1)
                insertion_index = frontmatter_end_index + 1
            except ValueError:
                insertion_index = 0

        # 2. Scan for the first image AFTER the frontmatter
        for i in range(insertion_index, len(lines)):
            line = lines[i].strip()
            if line.startswith(self._IMAGE_START_TAG):
                insertion_index = i + 1
                break

        # 3. Prepare the React Component content
        formatted_tldr = (
            f"\n{self._TLDR_TAG}\n"
            f"{tldr_text}\n"
            f"</TLDR>\n"
        )

        # 4. Insert and Save
        lines.insert(insertion_index, formatted_tldr)
        new_content = "\n".join(lines)

        self._file_path.write_text(new_content, encoding="utf-8")
        print(f"Successfully injected React <TLDR> into {self._file_path.name} (Line {insertion_index + 1})")
```

**src/components/TLDR/index.tsx**

```typescript
import { useMemo, type JSX, type ReactNode } from "react";
import Head from "@docusaurus/Head";
import MobileQuickLinks from "@site/src/components/Blog/MobileQuickLinks";
import styles from "./styles.module.css";
import { translate } from "@docusaurus/Translate";

// Recursively extracts plain text from React nodes for JSON-LD abstract
function extractText(node: unknown): string {
  if (typeof node === "string") return node;
  if (typeof node === "number") return String(node);
  if (Array.isArray(node)) return node.map(extractText).join("");
  if (node && typeof node === "object") {
    const children = (node as { props?: { children?: unknown } }).props?.children;
    if (children) return extractText(children);
  }
  return "";
}

interface Props {
  children: ReactNode;
}

// Injects JSON-LD so search engines read the abstract via Schema.org BlogPosting
export default function TLDR({ children }: Props): JSX.Element {
  const structuredData = useMemo(
    () => ({
      "@context": "https://schema.org",
      "@type": "BlogPosting",
      abstract: extractText(children),
    }),
    [children],
  );

  return (
    <>
      <Head>
        <script type="application/ld+json">{JSON.stringify(structuredData)}</script>
      </Head>

      <div
        className={styles.tldrContainer}
        role="region"
        aria-label={translate({ id: "tldr.ariaLabel", message: "Article Summary" })}
      >
        <span
          role="img"
          aria-label={translate({ id: "tldr.iconLabel", message: "lightning" })}
          className={styles.tldrIcon}
        >
          ⚡
        </span>

        <div className={styles.tldrBody}>
          <strong className={styles.tldrTitle}>TL;DR</strong>
          <div className={styles.tldrContent}>{children}</div>
        </div>
      </div>

      {/*
        The TL;DR is the one block every post opens with, which makes it the only reliable
        anchor for a "read next" hint placed *after* the reader has confirmed the article is
        the right one. MobileQuickLinks decides on its own whether it has anything to show.
      */}
      <MobileQuickLinks />
    </>
  );
}
```

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

```css
/*
 * TL;DR — was borrowing Infima's "alert alert--info" utility classes, which is
 * where its unexplained Docusaurus blue came from. The summary is the first
 * block of every article on the site: it should carry the site's own colour,
 * not a framework default.
 */

.tldrContainer {
  composes: mk-callout from global;
  composes: mk-callout--key from global;
}

/*
 * font-variant-emoji: text asks for the monochrome TEXT presentation of the
 * lightning character, so it takes --mk-on-line from .mk-dot like every other
 * dot glyph in the site. Without it the emoji keeps its own yellow-blue
 * colours on the terracotta disc — the one bicolour pastille on the page.
 * index.tsx needs no change.
 */
.tldrIcon {
  composes: mk-dot from global;
  font-size: 0.9rem;
  font-variant-emoji: text;
}

.tldrBody {
  min-width: 0;
}

.tldrTitle {
  composes: mk-eyebrow from global;
  display: block;
  margin-bottom: 0.25rem;
  color: inherit;
}

/*
 * The old rule indented the summary by a hardcoded 2.5rem to clear the icon,
 * and dimmed it with opacity: 0.9. Both are gone: the flex row aligns the text
 * for free, and a summary is the last thing on the page that should be faded.
 */
.tldrContent {
  font-size: 0.95rem;
  line-height: 1.65;
}

.tldrContent > :last-child {
  margin-bottom: 0;
}

.tldrContent a {
  color: var(--tone-line);
  text-decoration: underline;
  text-underline-offset: 2px;
}
```

> **Limitations de Gemini:**
>
> Selon votre plan Gemini AI, il peut y avoir des limites sur le nombre de requêtes ou la longueur du texte traité. Vérifiez bien les détails de votre plan pour éviter les frais inattendus ou les interruptions.
>
> Actuellement, mon propre plan me permet de faire 20 appels par jour.

> **src/theme/MDXComponents.js:**
>
> N'oubliez pas de mettre à jour votre fichier `src/theme/MDXComponents.js` pour y inclure le nouveau composant `TLDR`, afin que Docusaurus sache comment le rendre. Si vous n'avez pas encore ce fichier, créez-le. Voici son contenu :
>
> **src/theme/MDXComponents.js**
>
> ```javascript
> // [...]
>
> // highlight-next-line
> import TLDR from "@site/src/components/TLDR";
>
> export default {
>   // Reusing the default mapping
>   ...MDXComponents,
>   // [...]
>   // highlight-next-line
>   TLDR,
>   // [...]
> };
> ```
