<!--
  canonical-url: https://www.avonture.be/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.
-->

# A Docusaurus component that alerts readers the article is over a year old

> Displays a warning banner when the page content is over a year old

---
![A Docusaurus component that alerts readers the article is over a year old](/img/v2/old_blog_notice.webp)

**TL;DR**

This article demonstrates creating a Docusaurus component to alert readers when a blog post is over a year old. It involves building a React component and "swizzling" the `BlogPostPage` template to inject this notice. The component determines an article's age by checking its `last_update` or `date` field in the front matter.

In this article, let's see how we can display a small frame "Warning: This article is at least a year old, which in tech years is basically prehistoric."

We'll create a React component for Docusaurus, and we'll inject it in our blog post page so, once in place, everything will work automagically.

Here it is in action, on an old post of this very blog:

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

Let's build it.

## Create the component

You'll need to create two new files in your own Docusaurus site:

**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;
}
```

## Override BlogPostPage template

In terms of Docusaurus, we'll need to **swizzle** the page that is responsible for rendering a post. That page is called `BlogPostPage`.

To do this, please start a console and run `yarn swizzle @docusaurus/theme-classic BlogPostItem/Content`.

From now, you'll have a new file on your disk: `src/theme/BlogPostItem/Content/index.js`

> **You may already have that file:**
>
> [Showing the changelog of your post](/blog/docusaurus-changelog) swizzles exactly the same file. If you've followed that article, just add the new component next to the existing one instead of swizzling again.

Below, the original content of the file (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>
  );
}
```

To inject our new `OldPostNotice` component, please edit the file like this (see highlighted lines):

**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
    </>
  );
}
```

Save the file and refresh your blog. You should get the same banner already teased at the top of this article.

> **Information:**
>
> The component is using the `date` field that you have to mention in your YAML front matter.
>
> So, in each of your `.md` post, you should have a YAML block like this:
>
> ```yaml
> ---
> title: Your blog post article
> authors: [you]
> date: 2025-09-30
> ---
> ```

## Marking a reviewed post

If you revisit an old article and confirm the content is still accurate, you can add a `review_date` field to the front matter:

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

When `review_date` is present **and less than a year old**, the component swaps the orange warning for a green confirmation banner:

> ✅ This article is over a year old but was reviewed on July 30, 2026 — the content is still accurate.

If `review_date` itself is over a year old, the standard warning reappears — because a stale review is no better than no review at all.

## Position of the warning

The notice will be placed before the post content. On my blog, I've manipulated the code below to extract the very first image (i.e. the banner) so I can display it first, then the notice information, then the rest of the article. But that's probably too complex right now. We'll see this in a future article.

*The banner image is treated differently from the other ones for exactly the same reason in [Change how Docusaurus will create img tags](/blog/docusaurus-override-img): the first image of a post is special and should be left alone.*

```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>
```
