<!--
  canonical-url: https://www.avonture.be/fr/blog/docusaurus-old-notice
  published:     2025-10-03
  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.
-->

# Un composant Docusaurus qui prévient le lecteur que l'article a plus d'un an

> Affiche une bannière d'avertissement quand le contenu de la page a plus d'un an

---
![Un composant Docusaurus qui prévient le lecteur que l'article a plus d'un an](/img/v2/old_blog_notice.webp)

**TL;DR**

Cet article montre comment créer un composant Docusaurus qui prévient le lecteur quand un billet de blog a plus d'un an. Il s'agit de construire un composant React et de « swizzler » le template `BlogPostPage` pour y injecter cet avis. Le composant détermine l'âge d'un article en regardant son champ `last_update` ou `date` dans le front matter.

Dans cet article, voyons comment afficher un petit cadre « Attention : cet article a au moins un an, ce qui, en années tech, relève quasiment de la préhistoire. »

Nous allons créer un composant React pour Docusaurus et l'injecter dans la page de nos billets de blog : une fois en place, tout fonctionnera comme par magie.

Le voici en action, sur un vieux billet de ce blog :

> Screenshot — https\://www\.avonture.be/blog/joomla-show-table
>
> Old post notice in action

C'est parti.

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

Vous devez créer deux nouveaux fichiers dans votre propre site Docusaurus :

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

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

```typescript
/**
 * OldPostNotice
 *
 * A Docusaurus blog component that displays a visual warning if the article
 * is older than one year. This helps readers understand that the content may
 * be outdated and should be interpreted with caution.
 *
 * Features:
 * - Uses `useBlogPost` to access the post's metadata and publication date.
 * - Calculates whether the post is older than one year.
 * - Renders the shared .mk-callout primitive (see src/css/theme.css) through
 *   styles.module.css — NOT Docusaurus's own alert classes.
 *
 * Usage:
 * Import and include this component inside your swizzled `BlogPostItem/Content/index.js`
 * to automatically show the notice on older posts.
 *
 * Styling:
 * Additional styles can be customized via `styles.module.css` in the same folder.
 */

import type { JSX } from "react";
import { useBlogPost } from "@docusaurus/plugin-content-blog/client";
import { useDateTimeFormat } from "@docusaurus/theme-common/internal";
import Translate from "@docusaurus/Translate";
import styles from "./styles.module.css";

export default function OldPostNotice(): JSX.Element | null {
  const { metadata } = useBlogPost();
  const { date, frontMatter } = metadata;

  // Declared here, above every early return: it is a hook. `timeZone: "UTC"` because
  // `review_date` is a date-only front-matter string parsed as UTC midnight.
  const dateTimeFormat = useDateTimeFormat({
    year: "numeric",
    month: "long",
    day: "numeric",
    timeZone: "UTC",
  });

  // `updates` and `review_date` are this blog's own frontmatter fields (see CLAUDE.md),
  // not part of Docusaurus's own BlogPostFrontMatter type — hence the casts.
  const updates = frontMatter.updates as { date: string; note: string }[] | undefined;
  const reviewDate = frontMatter.review_date as string | undefined;

  let lastUpdated = date;

  if (updates && updates.length > 0) {
    const mostRecentUpdate = [...updates].sort(
      (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
    )[0];
    lastUpdated = mostRecentUpdate.date;
  }

  const postDate = new Date(lastUpdated);
  const oneYearAgo = new Date();
  oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);

  const isOld = postDate < oneYearAgo;

  if (!isOld) {
    return null;
  }

  if (reviewDate) {
    const reviewDateObj = new Date(reviewDate);
    const isReviewRecent = reviewDateObj >= oneYearAgo;

    if (isReviewRecent) {
      // `useDateTimeFormat` follows the locale being rendered; the hardcoded "en-US" it
      // replaces printed "September 14, 2026" inside an otherwise French notice.
      const formattedDate = dateTimeFormat.format(reviewDateObj);

      return (
        <div className={styles.reviewedPostNotice} role="status">
          <span className={styles.icon} aria-hidden="true">
            ✓
          </span>
          <p className={styles.body}>
            <Translate
              id="blog.oldPostNotice.reviewed"
              values={{ date: <strong>{formattedDate}</strong> }}
            >
              {"This article is over a year old but was reviewed on {date} — the content is still accurate."}
            </Translate>
          </p>
        </div>
      );
    }
  }

  return (
    <div className={styles.oldPostNotice} role="status">
      <span className={styles.icon} aria-hidden="true">
        !
      </span>
      <p className={styles.body}>
        <Translate
          id="blog.oldPostNotice.message"
          description="The warning message displayed on old blog posts"
        >
          This article is over a year old. The information may be outdated.
        </Translate>
      </p>
    </div>
  );
}
```

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

```css
/*
 * OldPostNotice — was using Infima's warning/success contrast pairs, a fifth
 * colour system in the site. Both notices are callouts now, so a reader meets
 * the same object whether the message comes from an article's author or from
 * the site itself.
 *
 * index.tsx no longer adds "alert alert--warning" / "alert alert--success" on
 * top of these classes: Infima's .alert sets its own background, full border
 * and text colour, so the two systems were stacking and neither won cleanly.
 */

.oldPostNotice {
  composes: mk-callout from global;
  composes: mk-callout--warn from global;
  margin: 0 0 1.5rem;
}

.reviewedPostNotice {
  composes: mk-callout from global;
  composes: mk-callout--tip from global;
  margin: 0 0 1.5rem;
}

.icon {
  composes: mk-dot from global;
}

.body {
  min-width: 0;
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.6;
}

/* The date inside the "reviewed" notice. The old rule added a right margin to
 * every <strong>, which also indented the last word of a sentence ending on
 * one — the flex row and the sentence's own spaces handle this. */
.body strong {
  font-weight: 600;
}
```

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

Côté Docusaurus, nous devons **swizzler** la page responsable du rendu d'un billet. Cette page s'appelle `BlogPostPage`.

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

Dès maintenant, vous avez un nouveau fichier sur votre disque : `src/theme/BlogPostItem/Content/index.js`

> **Vous avez peut-être déjà ce fichier:**
>
> [Showing the changelog of your post](/blog/docusaurus-changelog) swizzle exactement le même fichier. Si vous avez suivi cet article, ajoutez simplement le nouveau composant à côté de l'existant au lieu de swizzler à nouveau.

Ci-dessous, le contenu original du fichier (Docusaurus v3.8.1) :

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

```javascript
import React from 'react';
import clsx from 'clsx';
import {blogPostContainerID} from '@docusaurus/utils-common';
import {useBlogPost} from '@docusaurus/plugin-content-blog/client';
import MDXContent from '@theme/MDXContent';
export default function BlogPostItemContent({children, className}) {
  const {isBlogPostPage} = useBlogPost();
  return (
    <div
      // This ID is used for the feed generation to locate the main content
      id={isBlogPostPage ? blogPostContainerID : undefined}
      className={clsx('markdown', className)}>
      <MDXContent>{children}</MDXContent>
    </div>
  );
}
```

Pour injecter notre nouveau composant `OldPostNotice`, éditez le fichier comme ceci (voir les lignes surlignées) :

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

```javascript
import React from 'react';
import clsx from 'clsx';
import {blogPostContainerID} from '@docusaurus/utils-common';
import {useBlogPost} from '@docusaurus/plugin-content-blog/client';
import MDXContent from '@theme/MDXContent';
// highlight-next-line
import OldPostNotice from "@site/src/components/Blog/OldPostNotice";

export default function BlogPostItemContent({children, className}) {
  const {isBlogPostPage} = useBlogPost();
  return (
    // highlight-next-line
    <>
      // highlight-next-line
      <OldPostNotice />
      <div
        // This ID is used for the feed generation to locate the main content
        id={isBlogPostPage ? blogPostContainerID : undefined}
        className={clsx('markdown', className)}>
        <MDXContent>{children}</MDXContent>
      </div>
    // highlight-next-line
    </>
  );
}
```

Sauvez le fichier et rafraîchissez votre blog. Vous devriez obtenir la même bannière que celle montrée en début d'article.

> **Information:**
>
> Le composant utilise le champ `date` que vous devez mentionner dans votre front matter YAML.
>
> Donc, dans chacun de vos billets `.md`, vous devez avoir un bloc YAML comme celui-ci :
>
> ```yaml
> ---
> title: Your blog post article
> authors: [you]
> date: 2025-09-30
> ---
> ```

## Marquer un billet relu {#marking-a-reviewed-post}

Si vous revenez sur un vieil article et confirmez que le contenu est toujours exact, vous pouvez ajouter un champ `review_date` dans le front matter :

```yaml
---
date: 2023-05-10
review_date: 2026-07-30
---
```

Quand `review_date` est présent **et date de moins d'un an**, le composant remplace l'avertissement orange par une bannière verte de confirmation :

> ✅ Cet article a plus d'un an mais a été relu le 30 juillet 2026 — le contenu est toujours exact.

Si `review_date` a lui-même plus d'un an, l'avertissement standard réapparaît — parce qu'une relecture périmée ne vaut pas mieux que pas de relecture du tout.

## Position de l'avertissement {#position-of-the-warning}

L'avis sera placé avant le contenu du billet. Sur mon blog, j'ai manipulé le code ci-dessous pour extraire la toute première image (c'est-à-dire la bannière) afin de l'afficher en premier, puis l'avis, puis le reste de l'article. Mais c'est sans doute trop complexe pour l'instant. Nous verrons ça dans un prochain article.

*L'image de bannière est traitée différemment des autres pour exactement la même raison que dans [Change how Docusaurus will create img tags](/blog/docusaurus-override-img) : la première image d'un billet est spéciale et doit être laissée telle quelle.*

```jsx
<div
  // This ID is used for the feed generation to locate the main content
  id={isBlogPostPage ? blogPostContainerID : undefined}
  className={clsx('markdown', className)}>
  <MDXContent>{children}</MDXContent>
</div>
```
