<!--
  canonical-url: https://www.avonture.be/fr/blog/docusaurus-changelog
  published:     2025-11-18
  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.
-->

# Afficher le changelog de votre article

> Afficher le changelog de votre article

---
![Afficher le changelog de votre article](/img/v2/changelog.webp)

**TL;DR**

Cet article montre comment mettre en place un changelog détaillé pour les articles de blog Docusaurus, au-delà du champ `last_update` par défaut. Il s'agit de créer un composant React personnalisé qui lit un tableau `updates` depuis le frontmatter YAML de l'article, ce qui permet d'avoir plusieurs notes datées. La solution demande de créer ce composant puis de swizzler le thème `BlogPostItem/Content` pour afficher automatiquement l'historique des mises à jour.

En tant qu'auteur de blog, il vous arrive forcément de retoucher d'anciens articles pour ajouter ou clarifier un passage. Ou simplement pour supprimer une information obsolète.

Docusaurus propose l'entrée `last_update` ([source](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-docs#last_update)) comme illustré ci-dessous, mais vous ne pouvez indiquer qu'une seule date, sans description.

```yaml
last_update:
  date: 1/1/2000
  author: custom author name
```

Créons un tout petit composant pour améliorer ça.

## Le résultat {#the-result}

Une fois le composant branché (voir plus bas), il suffit d'ajouter une entrée `updates` dans le frontmatter d'un article pour obtenir ceci, automatiquement :

![Demo](./images/sample.webp)

## Pourquoi ça marche {#why-it-works}

* Le composant lit un tableau `updates` directement dans le frontmatter YAML de l'article — aucun fichier de changelog séparé à maintenir.
* Contrairement au `last_update` natif de Docusaurus, il accepte plusieurs entrées, chacune avec sa propre `date` et sa `note`.
* Une fois swizzlé, il s'affiche automatiquement sur chaque article qui contient une entrée `updates` — rien à appeler manuellement article par article.

## Installation {#installation}

Nous allons devoir créer un nouveau composant et swizzler le BlogPostPage, afin que le composant soit exécuté automatiquement.

### Créer le composant Updated {#create-the-updated-component}

Créez un nouveau dossier `src/components/Blog/Updated` dans lequel vous allez créer deux fichiers : `index.tsx` et `styles.module.css`.

**src/components/Blog/Updated/index.tsx**

```typescript
import type { JSX } from "react";
import clsx from "clsx";
import Translate from "@docusaurus/Translate";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { parseMarkdown } from "@site/src/components/Blog/utils/markdown";

import styles from "./styles.module.css";

interface UpdateEntry {
  date: string;
  note: string;
}

interface Props {
  updates?: UpdateEntry[];
}

export default function Updated({ updates }: Props): JSX.Element | null {
  const { i18n } = useDocusaurusContext();

  if (!updates || updates.length === 0) return null;

  const sortedUpdates = [...updates].sort(
    (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
  );

  return (
    <div className={clsx(styles.revisionHistoryContainer, "margin-top--lg")}>
      <h3 className={styles.revisionHistoryTitle}>
        <span aria-hidden="true">📜</span>{" "}
        <Translate id="blog.updated.changelog">Changelog</Translate>
      </h3>
      <div className={styles.timeline}>
        {sortedUpdates.map((update, i) => {
          const isLatest = i === 0;
          return (
            <div
              key={`${update.date}-${i}`}
              className={clsx(
                styles.timelineItem,
                isLatest && styles.timelineItemLatest,
              )}
            >
              <div className={styles.timelineIcon}>
                <span aria-hidden="true">{isLatest ? "✨" : "📅"}</span>
              </div>
              <div className={styles.timelineContent}>
                <div className={styles.timelineDate}>
                  <time dateTime={update.date}>
                    {new Date(update.date).toLocaleDateString(
                      i18n.currentLocale,
                      { year: "numeric", month: "long", day: "numeric" },
                    )}
                  </time>
                  {isLatest && (
                    <span className={styles.latestBadge}>
                      <Translate id="blog.updated.latest">Latest</Translate>
                    </span>
                  )}
                </div>
                <div
                  className={styles.timelineDescription}
                  dangerouslySetInnerHTML={{
                    __html: parseMarkdown(update.note),
                  }}
                />
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}
```

**src/components/Blog/Updated/styles.module.css**

```css
/*
 * Updated — the timeline structure is good and is kept as-is. What changed:
 * it no longer borrows --card-radius from PostCard's module (see that file),
 * the timeline node is the shared .mk-dot, and the title is an eyebrow rather
 * than a 1.1rem bold heading competing with the article's own.
 */

.revisionHistoryContainer {
  composes: mk-surface from global;
  padding: 1.25rem 1.375rem;
}

.revisionHistoryTitle {
  composes: mk-eyebrow from global;
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin: 0 0 1rem;
}

.timeline {
  position: relative;
}

/* The connecting line: centred on the 26px node (13px) rather than the old
 * 30px one (15px). */
.timeline::before {
  position: absolute;
  top: 4px;
  left: 13px;
  width: 2px;
  height: calc(100% - 8px);
  content: "";
  background: var(--mk-border);
}

.timelineItem {
  position: relative;
  display: flex;
  min-height: 26px;
  margin-bottom: 1.25rem;
}

.timelineItem:last-child {
  margin-bottom: 0;
}

.timelineIcon {
  composes: mk-dot from global;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  color: var(--mk-on-line);
  background: var(--mk-n-300);
}

/* The latest revision is the only filled terracotta node, with a halo in the
 * key tone — the halo used to be a color-mix() of the primary at 15%. */
.timelineItemLatest .timelineIcon {
  background: var(--mk-primary);
  box-shadow: 0 0 0 4px var(--mk-key-bg);
}

.timelineContent {
  flex: 1;
  padding-top: 0.15rem;
  padding-left: 40px;
}

.timelineDate {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.3rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--mk-ink-soft);
  font-variant-numeric: tabular-nums;
}

.timelineItemLatest .timelineDate {
  color: var(--mk-primary);
}

.latestBadge {
  composes: mk-pill from global;
}

.timelineDescription {
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--mk-ink);
}
```

En résumé, le composant vérifie si le frontmatter YAML de votre article de blog contient une entrée `updates`. Si c'est le cas, il la lit. Ce doit être un tableau avec deux informations : une `date` et une `note`.

### Surcharger le template BlogPostPage {#override-the-blogpostpage-template}

Vérifiez d'abord si vous n'avez pas déjà le fichier `src/theme/BlogPostItem/Content/index.js`. Si vous êtes un lecteur de ce blog et que vous avez déjà personnalisé Docusaurus, vous l'avez peut-être déjà : [le composant qui signale qu'un article a plus d'un an](/blog/docusaurus-old-notice) swizzle exactement ce même fichier.

Sinon, ouvrez une console et lancez `yarn swizzle @docusaurus/theme-classic BlogPostItem/Content`.

À partir de maintenant, vous avez un nouveau fichier sur votre disque : `src/theme/BlogPostItem/Content/index.js`.

Nous devons injecter notre nouveau composant `Updated`, éditez le fichier `index.js` pour y insérer le code nécessaire.

Voici les lignes minimales à ajouter au bon endroit dans votre propre fichier.

**src/theme/BlogPostItem/Content/index.js**

```javascript
// [...]
import Updated from "@site/src/components/Blog/Updated";

export default function BlogPostContent({ children }) {
  // [...]
  const { metadata, isBlogPostPage } = useBlogPost();
  // [...]

  return (
    <div>
      // [...]
      {isBlogPostPage && (
        <>
          // [...]
          <Updated updates={metadata.frontMatter.updates} />
        </>
      )}
      // [...]
    </div>
  );
}
```

## Autres démos {#more-demos}

### Utiliser le composant {#use-the-component}

Éditez maintenant n'importe lequel de vos articles existants et ajoutez une entrée `updates` dans votre frontmatter YAML, par exemple

```yaml
updates:
  - date: 2024-11-19
    note: review Dockerfile, use Quarto 1.6.36.
  - date: 2024-12-01
    note: Added new theme options for blog posts.
  - date: 2024-12-15
    note: Fixed bug with image rendering on mobile devices.
  - date: 2025-01-05
    note: Implemented search functionality for documentation pages.
  - date: 2025-01-20
    note: Optimized build process for faster deployment times.
```

Enregistrez l'article et rafraîchissez votre page web. Si tout est correct, vous verrez l'information dans votre navigateur — le même rendu que celui présenté en haut de cet article.

N'hésitez pas à modifier `src/components/Blog/Updated/styles.module.css` pour coller à vos propres préférences de style.

## Conclusion {#conclusion}

Un petit tableau lu dans le frontmatter et un seul fichier de thème swizzlé remplacent la date unique de `last_update` par un vrai changelog à plusieurs entrées — les lecteurs voient exactement ce qui a changé et quand, sans que vous ayez à maintenir un fichier séparé.

Ça se marie bien avec [l'avertissement affiché sur les articles de plus d'un an](/blog/docusaurus-old-notice) : le premier dit *ce qui* a changé, le second dit *quand* c'est arrivé pour la dernière fois.
