<!--
  canonical-url: https://www.avonture.be/fr/blog/docusaurus-bluesky-share
  published:     2025-08-13
  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.
-->

# Créer notre propre composant React Docusaurus et proposer un bouton « Partager sur Bluesky »

> Créez un composant React Docusaurus personnalisé qui ajoute un bouton « Partager sur Bluesky » à vos articles de blog.

---
![Créer notre propre composant React Docusaurus et proposer un bouton « Partager sur Bluesky »](/img/v2/docusaurus_bluesky.webp)

**TL;DR**

Cet article vous guide dans la création d'un bouton « Partager sur Bluesky »
pour votre blog Docusaurus. Vous apprendrez à utiliser la fonctionnalité
swizzle de Docusaurus pour surcharger le thème par défaut des articles, à
créer un composant React personnalisé pour le bouton, puis à l'intégrer dans
vos articles. Nous construirons le composant étape par étape, avec le CSS et
l'icône SVG nécessaires.

Il y a neuf mois, j'avais activé la possibilité d'écrire des commentaires sous mes articles grâce à **Giscus** (voir [Adding a comment area](/blog/site-creation)) mais seules les personnes ayant un compte GitHub pouvaient utiliser cette fonctionnalité et donc, presque personne ne l'a fait.

J'ai donc passé près de neuf mois sans donner aux lecteurs de ce blog l'occasion d'interagir avec moi, ce qui est un peu frustrant et nous empêche d'échanger des idées et de nous enrichir mutuellement.

Depuis quelques semaines, je suis présent sur [Bluesky](https://bsky.app/profile/avonture.be) et... pourquoi ne pas ajouter un bouton *Partager sur Bluesky* sous chaque article ?

L'idée n'est ni révolutionnaire ni compliquée alors, jouons...

Dans ce premier article d'une série de deux, nous allons voir comment créer notre composant React Docusaurus. Avant d'écrire cet article, je ne savais pas comment faire et maintenant, si : c'est très simple.

**Quick Jump:** [Le résultat en action](#result) | [Tous les fichiers d'un coup d'œil](#all-files-at-a-glance)

## Résultat {#result}

Et, tadaaa 🎉🎉🎉, voici un bouton « Partager sur Bluesky » en action, sous un article de blog :

> Screenshot — http\://localhost/
>
> ![Le bouton de partage Bluesky](./images/share_button.webp)

C'est tout l'objet de cet article : un petit composant React personnalisé, swizzlé dans le `BlogPostItem` de Docusaurus, avec rien de plus exotique qu'un `<button>` et une icône SVG. Construisons-le.

> **Information:**
>
> Dans un second article (voir [Show Bluesky comments on Docusaurus and, too, number of likes/reposts](/blog/docusaurus-bluesky-comments)), nous sommes allés plus loin : récupérer le nombre de likes, de reposts et de commentaires publiés sur Bluesky et les afficher sous nos articles sous forme d'une seule « engagement facepile » unifiée.

Voici à quoi ressemble ce bouton aujourd'hui, en direct sur ce blog, pour un article récent qui n'a encore récolté aucune interaction Bluesky :

> Screenshot — http\://localhost/
>
> ![Le bouton de partage, en direct sur le blog aujourd'hui](./images/share_button_live.webp)

Comme je n'avais absolument aucune idée de par où commencer, demandons à une IA de générer un squelette. J'ai demandé quelque chose comme « *Bonjour mon ami, j'aimerais afficher un bouton de partage Bluesky sous mes articles sur mon blog Docusaurus. Donne-moi un squelette, s'il te plaît.* »

Une fois le code généré par l'IA, il est temps de lire, comprendre et faire quelques tests.

## Nous devons surcharger la façon dont l'article est rendu par Docusaurus {#we-need-to-override-how-the-article-is-rendered-by-docusaurus}

J'ai donc appris que je devais créer un fichier appelé `src/theme/BlogPostItem/index.js` et simplement y mettre du contenu.

L'IA me donne le contenu mais... hé mon ami, d'où vient ce contenu ? Après quelques recherches, j'apprends qu'il s'agit d'une surcharge du template standard `@docusaurus/theme-classic`.

J'apprends aussi qu'on peut **extraire** le fichier original grâce à l'action appelée **Swizzle**.

> **Information:**
>
> Dans la terminologie Docusaurus, Swizzle peut se comprendre comme « surcharger » : nous allons extraire, ici, un template pour ensuite pouvoir le surcharger.

Le fichier `src/theme/BlogPostItem/index.js` va **personnaliser la mise en page et le contenu de chaque article de blog** donc, en modifiant ce fichier, je peux changer l'apparence de tous les articles de mon blog.

Puisque je veux ajouter un **bouton de partage**, c'est le candidat parfait. L'IA a raison.

## Extraire le fichier original {#extract-the-original-file}

Ouvrez une console et assurez-vous d'être dans le dossier racine de votre blog.

Dans votre console, lancez `yarn docusaurus swizzle @docusaurus/theme-classic BlogPostItem`.

Lorsque la question est posée, sélectionnez `Javascript`, puis **`Eject`** et enfin `YES`.

![Swizzle du layout BlogPostItem](./images/swizzle_blogpostitem.webp)

Comme vous pouvez le voir, beaucoup de fichiers vont être créés dans la structure de répertoires de notre Docusaurus.

### Garder les choses sous contrôle {#keep-things-under-control}

Comme vous le voyez à l'écran, vous allez extraire beaucoup de fichiers. **Gardons les choses sous contrôle : nous voulons juste ajouter un bouton de partage sous notre article, rien de plus.**

Regardez votre site Docusaurus maintenant ; allez dans le dossier `src/theme/BlogPostItem` et constatez que, oui, vous avez maintenant beaucoup de fichiers et de sous-dossiers. Supprimez simplement tous les sous-dossiers ; nous ne voulons pas les modifier, c'est-à-dire que nous voulons conserver les originaux. Ainsi, la prochaine fois que vous installerez une version plus récente de Docusaurus, vous resterez à jour.

Conservez uniquement le fichier `index.js` créé par la commande `swizzle`. Voici celui que j'ai obtenu avec ma version actuelle de Docusaurus :

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

```javascript
import React from "react";
import clsx from "clsx";
import { useBlogPost } from "@docusaurus/plugin-content-blog/client";
import BlogPostItemContainer from "@theme/BlogPostItem/Container";
import BlogPostItemHeader from "@theme/BlogPostItem/Header";
import BlogPostItemContent from "@theme/BlogPostItem/Content";
import BlogPostItemFooter from "@theme/BlogPostItem/Footer";
// apply a bottom margin in list view
function useContainerClassName() {
  const { isBlogPostPage } = useBlogPost();
  return !isBlogPostPage ? "margin-bottom--xl" : undefined;
}
export default function BlogPostItem({ children, className }) {
  const containerClassName = useContainerClassName();
  return (
    <BlogPostItemContainer className={clsx(containerClassName, className)}>
      <BlogPostItemHeader />
      <BlogPostItemContent>{children}</BlogPostItemContent>
      <BlogPostItemFooter />
    </BlogPostItemContainer>
  );
}
```

## Jouons et ajoutons un message stupide à chaque article {#playing-and-adding-a-stupid-message-to-every-blog-post}

Avant d'aller plus loin, faisons un test : est-ce bien le fichier à modifier pour notre besoin ?

Jouons et ajoutons une ligne juste après le titre de notre article. Regardez l'exemple ci-dessous ; je viens d'ajouter une nouvelle ligne :

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

```javascript
import React from "react";
import clsx from "clsx";
import { useBlogPost } from "@docusaurus/plugin-content-blog/client";
import BlogPostItemContainer from "@theme/BlogPostItem/Container";
import BlogPostItemHeader from "@theme/BlogPostItem/Header";
import BlogPostItemContent from "@theme/BlogPostItem/Content";
import BlogPostItemFooter from "@theme/BlogPostItem/Footer";
// apply a bottom margin in list view
function useContainerClassName() {
  const { isBlogPostPage } = useBlogPost();
  return !isBlogPostPage ? "margin-bottom--xl" : undefined;
}
export default function BlogPostItem({ children, className }) {
  const containerClassName = useContainerClassName();
  return (
    <BlogPostItemContainer className={clsx(containerClassName, className)}>
      <BlogPostItemHeader />
      // highlight-next-line
      {/* Just after the blog post title, we'll add a "Are you ready" text */}
      // highlight-next-line
      <strong style={{ color: "red" }}>
        Are you ready to update your BlogPostItem layout?
      </strong>
      <BlogPostItemContent>{children}</BlogPostItemContent>
      <BlogPostItemFooter />
    </BlogPostItemContainer>
  );
}
```

Sauvegardez le fichier, rafraîchissez la page dans votre navigateur et tadaaa 🎉🎉🎉... Oui, on voit ce texte sur chaque article.

Sur l'image ci-dessus, on peut voir notre message stupide :

> Screenshot — http\://localhost/
>
> ![Are you ready?](./images/are_you_ready.webp)

> **Note:**
>
> Si ça n'a pas fonctionné, arrêtez et relancez votre serveur Docusaurus. Dans mon cas (j'utilise Docker), il me suffit d'arrêter et de relancer mon container.

Donc, comme vous le voyez, c'est maintenant assez simple. Nous pouvons changer la mise en page de notre page Docusaurus juste en manipulant le fichier `index.js`.

## Il est temps de créer notre composant Docusaurus Bluesky {#time-to-create-our-bluesky-docusaurus-component}

Nous allons procéder étape par étape en gardant quelques points en tête :

**Points à retenir**

* Nous devons minimiser nos changements dans le fichier original `src/theme/BlogPostItem/index.js` ; le moins de modifications, le mieux ;

* Nous allons créer un composant Docusaurus dans `src/components/` pour permettre les interactions avec Bluesky (pensez à l'évolution du composant) donc, pour cette raison, nous mettrons tout ensemble (code, image, css) et

* Nous essaierons de garder les choses propres et maintenables pour le développeur, et configurables pour l'utilisateur.

### 1/5 La structure de notre composant Bluesky générique {#15-the-layout-of-our-bluesky-generic-component}

Le premier fichier que nous allons créer sera notre fichier `index.tsx`, c'est-à-dire notre composant Bluesky global.

Dans cet article, nous allons juste prévoir un bouton « Partager sur Bluesky » mais dans le second article, nous ajouterons d'autres fonctionnalités.

Ce `index.tsx` sera le **point d'entrée** du composant.

Créez le fichier `src/components/Bluesky/index.tsx` avec ce contenu :

**src/components/Bluesky/index.tsx**

```typescript
import BlueskyShare from "./share";
import styles from "./styles.module.css";

interface Props {
  metadata: {
    frontMatter?: {
      blueskyRecordKey?: string;
    };
  };
}

export default function Bluesky({ metadata }: Props) {
  return (
    <div className={styles.blueskyContainer}>
      <BlueskyShare metadata={metadata} />
    </div>
  );
}
```

**Voilà notre composant React `<Bluesky />` ! Simple, non ? Pour l'instant, c'est juste un div contenant un autre composant appelé `<BlueskyShare />`**.

Si vous regardez le code ci-dessus, nous devons créer deux nouveaux fichiers : `share.tsx` et `styles.module.css`.

### 2/5 Notre bouton « Partager sur Bluesky » {#25-our-share-on-bluesky-button}

Ok, maintenant, nous devons créer un deuxième fichier appelé `src/components/Bluesky/share.tsx` et y copier/coller ce contenu :

**src/components/Bluesky/share.tsx**

```typescript
import Icon from "./bluesky.svg";
import styles from "./styles.module.css";
import Translate, { translate } from "@docusaurus/Translate";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";

// Declared locally rather than imported from useBlueskyEngagement — this file (built in the
// series' first article) stands on its own before that hook exists in the tutorial's timeline.
interface Props {
  metadata: {
    title?: string;
    permalink?: string;
    frontMatter?: {
      blueskyRecordKey?: string;
    };
  };
}

export default function BlueskyShare({ metadata }: Props) {
  const { siteConfig } = useDocusaurusContext();
  const blueskyRecordKey = metadata?.frontMatter?.blueskyRecordKey;

  if (blueskyRecordKey) return null;

  if (!metadata.title || !metadata.permalink) {
    console.debug("<BlueskyShare> Missing required properties", { metadata });
    return null;
  }

  const shareLink =
    `https://bsky.app/intent/compose?text=` +
    `${encodeURIComponent(metadata.title)}%20${siteConfig.url}${encodeURIComponent(
      metadata.permalink,
    )}`;

  return (
    <a
      href={shareLink}
      target="_blank"
      rel="noopener noreferrer"
      className={styles.blueskyButton}
      aria-label={translate({
        id: "bluesky.share.ariaLabel",
        message: "Share this post on Bluesky",
      })}
    >
      {/* alt dropped: SVGProps has no such prop — was a no-op DOM attribute even before this
          migration, and the link's own aria-label already names the icon. */}
      <Icon className={styles.blueskyLogo} />
      <Translate id="bluesky.share.label">Share on Bluesky</Translate>
    </a>
  );
}
```

Comme vous le voyez, nous avons défini une fonction appelée `BlueskyShare` et cette fonction attend un paramètre appelé `metadata`.

> **Information:**
>
> Revenez au fichier `src/components/Bluesky/index.tsx` que nous venons de créer. Nous avions cette ligne : `<BlueskyShare metadata={metadata} />` donc, oui, nous passons bien ce paramètre.
>
> Le paramètre `metadata` est un objet initialisé par Docusaurus et qui contiendra toutes les données du front matter du fichier Markdown de l'article courant. Il inclut des propriétés comme title, description, date, tags, et tous les autres champs personnalisés que vous avez définis dans le front matter.

Que fait la fonction `BlueskyShare` ? En fait, nous allons juste proposer un bouton avec une url du type `https://bsky.app/intent/compose?text=<title>%20<url>` (où nous injecterons le titre et l'url de l'article).

Si vous regardez le code ci-dessus, nous devons créer un autre fichier : `bluesky.svg`.

### 3/5 Le logo Bluesky {#35-the-bluesky-logo}

Évidemment, il nous faut le logo Bluesky quelque part.

Créez un troisième fichier appelé `src/components/Bluesky/bluesky.svg` et copiez/collez ce contenu dedans :

**src/components/Bluesky/bluesky.svg**

```svg
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 600 530" xmlns="http://www.w3.org/2000/svg">
  <path d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z" fill="#1185fe"/>
</svg>
```

### 4/5 Et enfin notre CSS {#45-and-finally-our-css}

Et, très important, il nous faut du CSS. Créez un quatrième fichier appelé `src/components/Bluesky/styles.module.css` et copiez/collez ce contenu dedans :

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

```css
.blueskyLogo {
  width: 20px;
  height: 20px;
}

.blueskyContainer {
  margin: 2rem 0 0.5rem;
  padding: 1.1rem 1.25rem 1.4rem;
  border: 1px solid var(--ifm-color-emphasis-200);
  border-radius: 8px;
  background: var(--ifm-background-surface-color);
}

.blueskyHeadline {
  display: block;
  font-weight: 500;
  color: var(--ifm-color-content);
  margin-bottom: 0.75rem;
}

.blueskyAccountNote {
  display: block;
  margin-top: 0.6rem;
  font-size: smaller;
  color: var(--ifm-color-emphasis-600);
}

.blueskyCommentsContainer {
  border: 1px solid var(--ifm-color-emphasis-200);
  margin-top: 1rem;
  padding: 0.4rem 0.9rem;
  margin-bottom: 1.25rem;
}

.blueskyCommentContainer {
  margin-bottom: 1rem;
  background-color: var(--ifm-color-emphasis-100);
  padding: 0.8rem;
  border-radius: 6px;
  border-left: 2px solid var(--ifm-color-emphasis-300);
  color: var(--ifm-font-color-base);
}

.blueskyCommentHeader {
  display: flex;
  align-items: center;
  gap: 10px;
}

.blueskyCommentAuthorInfos {
  display: flex;
  flex-direction: column;
}

.blueskyCommentAvatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  margin-right: 0.5rem;
}

.blueskyCommentAuthorDisplayName {
  margin: 0;
  font-weight: bold;
}

.blueskyCommentAuthorHandle {
  margin: 0;
  font-size: smaller;
  font-weight: normal;
  color: var(--ifm-color-emphasis-600);
  display: block;
}

.blueskyCommentDate {
  margin-left: auto;
  font-size: smaller;
  color: var(--ifm-color-emphasis-600);
}

.blueskyCommentText {
  margin: 0.3rem 0 0;
}

.blueskyCommentLikes::before {
  content: "❤️\a0";
}

.blueskyCommentLikes::after {
  content: " ";
}

.blueskyCommentReposts::before {
  content: "🔁\a0";
}

.blueskyCommentReposts::after {
  content: " ";
}

.blueskyCommentLink::before {
  content: "→\a0";
}

.blueskyCommentLink::after {
  content: " ";
}

.blueskyCommentLink {
  margin-right: 0.25rem;
  display: inline-flex;
  align-items: center;
  color: var(--ifm-color-emphasis-600);
  text-decoration: none;
  margin-top: 0.25rem;
}

img.blueskyCommentEmbedThumb {
  max-height: 250px;
}

.blueskyCommentImages {
  margin-top: 0.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.blueskyCommentImage {
  max-width: 100%;
  height: auto;
  border-radius: 0.25rem;
  border: 1px solid var(--ifm-color-emphasis-300);
}

.blueskyNoCommentYet {
  color: var(--ifm-color-emphasis-600);
  font-size: 1rem;
  line-height: 1.5;
  text-align: center;
  padding: 20px;
  background-color: var(--ifm-color-emphasis-100);
  border: 1px solid var(--ifm-color-emphasis-300);
  border-radius: 8px;
  margin: 20px 0;
}

.blueskyNoCommentYetCTA {
  font-weight: bold;
  color: var(--ifm-color-primary-dark);
  cursor: pointer;
  text-decoration: underline;
}

[data-theme="dark"] .blueskyNoCommentYetCTA {
  color: var(--ifm-color-primary-light);
}

.blueskyPostLikes {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.blueskyLikersAvatars {
  display: inline-flex;
  align-items: center;
  margin-left: 0.4rem;
}

.blueskyLikersAvatarLink {
  margin-left: -1rem;
  line-height: 0;
}

.blueskyLikersAvatarLink:first-child {
  margin-left: 0;
}

.blueskyLikersAvatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 2px solid var(--ifm-background-color);
  display: block;
}

.blueskyLikersAvatarMore {
  margin-left: 0.4rem;
  font-size: smaller;
  color: var(--ifm-color-emphasis-600);
}

.blueskyEngagementRow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}

.blueskyButton {
  align-items: center;
  background-color: var(--ifm-color-emphasis-200);
  border-radius: 6px;
  border: 1px solid var(--ifm-color-emphasis-300);
  box-shadow: none;
  color: var(--ifm-font-color-base);
  display: inline-flex;
  font-size: 0.9rem;
  font-weight: 400;
  gap: 0.4rem;
  margin-bottom: 0.8rem;
  margin-right: 0.4rem;
  padding: 0.4rem 0.9rem;
  text-decoration: none;
  transition:
    background-color 0.2s ease,
    color 0.2s ease;
}

.blueskyButton:hover {
  background-color: var(--ifm-color-primary);
  border-color: var(--ifm-color-primary);
  color: white;
}
```

### 5/5 Dernière chose, nous devons implémenter notre composant dans Docusaurus {#55-last-thing-we-need-to-implement-our-component-in-docusaurus}

Notre composant Bluesky est prêt. Nous avons créé nos quatre fichiers.

Vous devriez avoir ceci :

```tree expanded=true showJSX=false debug=false title="src"
.
├── components/Bluesky
│   ├── bluesky.svg
│   ├── index.tsx
│   ├── share.tsx
│   └── styles.module.css
...
└── theme/BlogPostItem
    ├── index.js
```

Revenons à ce qui a été dit plus tôt : pour adapter la mise en page, Docusaurus utilise le fichier `src/theme/BlogPostItem/index.js`. Il est donc temps d'injecter notre composant fraîchement créé dans ce script.

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

```javascript
import React from "react";
import clsx from "clsx";
import { useBlogPost } from "@docusaurus/plugin-content-blog/client";
import BlogPostItemContainer from "@theme/BlogPostItem/Container";
import BlogPostItemHeader from "@theme/BlogPostItem/Header";
import BlogPostItemContent from "@theme/BlogPostItem/Content";
import BlogPostItemFooter from "@theme/BlogPostItem/Footer";

// highlight-next-line
// import our Bluesky component
// highlight-next-line
import Bluesky from "@site/src/components/Bluesky";

// apply a bottom margin in list view
function useContainerClassName() {
  const { isBlogPostPage } = useBlogPost();
  return !isBlogPostPage ? "margin-bottom--xl" : undefined;
}

export default function BlogPostItem({ children, className }) {
  // highlight-next-line
  // We need to retrieve the isBlogPostPage flag
  // highlight-next-line
  const { metadata, isBlogPostPage } = useBlogPost();
  const containerClassName = useContainerClassName();
  return (
    <BlogPostItemContainer className={clsx(containerClassName, className)}>
      <BlogPostItemHeader />
      <BlogPostItemContent>{children}</BlogPostItemContent>
      <BlogPostItemFooter />
      // highlight-next-line
      {/* Only display Bluesky components on the post page; not the blog view */}
      // highlight-next-line
      {isBlogPostPage && <Bluesky metadata={metadata} />}
    </BlogPostItemContainer>
  );
}
```

Félicitations ! Si vous allez sur une page de votre blog et que vous la rafraîchissez, vous verrez le bouton Bluesky en bas de page.

> **Note:**
>
> Si ça n'a pas fonctionné, arrêtez et relancez votre serveur Docusaurus. Dans mon cas (j'utilise Docker), il me suffit d'arrêter et de relancer mon container.

Rafraîchissez la page d'un article et vous verrez le même bouton que celui présenté en début d'article, maintenant en place sur votre propre site.

## Tous les fichiers d'un coup d'œil {#all-files-at-a-glance}

Voici l'ensemble complet des fichiers créés dans cet article. Utilisez le bouton ci-dessous pour les copier tous d'un coup, ou téléchargez-les sous forme de ZIP prêt à extraire :

**Project: Bluesky share button**

> **Note:**
>
> Ajoutez ces fichiers à votre site Docusaurus existant, puis relancez votre serveur de
> développement pour que le `BlogPostItem` swizzlé prenne en compte le nouveau composant.

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

```javascript
import React from "react";
import clsx from "clsx";
import { useBlogPost } from "@docusaurus/plugin-content-blog/client";
import BlogPostItemContainer from "@theme/BlogPostItem/Container";
import BlogPostItemHeader from "@theme/BlogPostItem/Header";
import BlogPostItemContent from "@theme/BlogPostItem/Content";
import BlogPostItemFooter from "@theme/BlogPostItem/Footer";

// highlight-next-line
// import our Bluesky component
// highlight-next-line
import Bluesky from "@site/src/components/Bluesky";

// apply a bottom margin in list view
function useContainerClassName() {
  const { isBlogPostPage } = useBlogPost();
  return !isBlogPostPage ? "margin-bottom--xl" : undefined;
}

export default function BlogPostItem({ children, className }) {
  // highlight-next-line
  // We need to retrieve the isBlogPostPage flag
  // highlight-next-line
  const { metadata, isBlogPostPage } = useBlogPost();
  const containerClassName = useContainerClassName();
  return (
    <BlogPostItemContainer className={clsx(containerClassName, className)}>
      <BlogPostItemHeader />
      <BlogPostItemContent>{children}</BlogPostItemContent>
      <BlogPostItemFooter />
      // highlight-next-line
      {/* Only display Bluesky components on the post page; not the blog view */}
      // highlight-next-line
      {isBlogPostPage && <Bluesky metadata={metadata} />}
    </BlogPostItemContainer>
  );
}
```

**src/components/Bluesky/index.tsx**

```typescript
import BlueskyShare from "./share";
import styles from "./styles.module.css";

interface Props {
  metadata: {
    frontMatter?: {
      blueskyRecordKey?: string;
    };
  };
}

export default function Bluesky({ metadata }: Props) {
  return (
    <div className={styles.blueskyContainer}>
      <BlueskyShare metadata={metadata} />
    </div>
  );
}
```

**src/components/Bluesky/share.tsx**

```typescript
import Icon from "./bluesky.svg";
import styles from "./styles.module.css";
import Translate, { translate } from "@docusaurus/Translate";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";

// Declared locally rather than imported from useBlueskyEngagement — this file (built in the
// series' first article) stands on its own before that hook exists in the tutorial's timeline.
interface Props {
  metadata: {
    title?: string;
    permalink?: string;
    frontMatter?: {
      blueskyRecordKey?: string;
    };
  };
}

export default function BlueskyShare({ metadata }: Props) {
  const { siteConfig } = useDocusaurusContext();
  const blueskyRecordKey = metadata?.frontMatter?.blueskyRecordKey;

  if (blueskyRecordKey) return null;

  if (!metadata.title || !metadata.permalink) {
    console.debug("<BlueskyShare> Missing required properties", { metadata });
    return null;
  }

  const shareLink =
    `https://bsky.app/intent/compose?text=` +
    `${encodeURIComponent(metadata.title)}%20${siteConfig.url}${encodeURIComponent(
      metadata.permalink,
    )}`;

  return (
    <a
      href={shareLink}
      target="_blank"
      rel="noopener noreferrer"
      className={styles.blueskyButton}
      aria-label={translate({
        id: "bluesky.share.ariaLabel",
        message: "Share this post on Bluesky",
      })}
    >
      {/* alt dropped: SVGProps has no such prop — was a no-op DOM attribute even before this
          migration, and the link's own aria-label already names the icon. */}
      <Icon className={styles.blueskyLogo} />
      <Translate id="bluesky.share.label">Share on Bluesky</Translate>
    </a>
  );
}
```

**src/components/Bluesky/bluesky.svg**

```svg
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 600 530" xmlns="http://www.w3.org/2000/svg">
  <path d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z" fill="#1185fe"/>
</svg>
```

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

```css
.blueskyLogo {
  width: 20px;
  height: 20px;
}

.blueskyContainer {
  margin: 2rem 0 0.5rem;
  padding: 1.1rem 1.25rem 1.4rem;
  border: 1px solid var(--ifm-color-emphasis-200);
  border-radius: 8px;
  background: var(--ifm-background-surface-color);
}

.blueskyHeadline {
  display: block;
  font-weight: 500;
  color: var(--ifm-color-content);
  margin-bottom: 0.75rem;
}

.blueskyAccountNote {
  display: block;
  margin-top: 0.6rem;
  font-size: smaller;
  color: var(--ifm-color-emphasis-600);
}

.blueskyCommentsContainer {
  border: 1px solid var(--ifm-color-emphasis-200);
  margin-top: 1rem;
  padding: 0.4rem 0.9rem;
  margin-bottom: 1.25rem;
}

.blueskyCommentContainer {
  margin-bottom: 1rem;
  background-color: var(--ifm-color-emphasis-100);
  padding: 0.8rem;
  border-radius: 6px;
  border-left: 2px solid var(--ifm-color-emphasis-300);
  color: var(--ifm-font-color-base);
}

.blueskyCommentHeader {
  display: flex;
  align-items: center;
  gap: 10px;
}

.blueskyCommentAuthorInfos {
  display: flex;
  flex-direction: column;
}

.blueskyCommentAvatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  margin-right: 0.5rem;
}

.blueskyCommentAuthorDisplayName {
  margin: 0;
  font-weight: bold;
}

.blueskyCommentAuthorHandle {
  margin: 0;
  font-size: smaller;
  font-weight: normal;
  color: var(--ifm-color-emphasis-600);
  display: block;
}

.blueskyCommentDate {
  margin-left: auto;
  font-size: smaller;
  color: var(--ifm-color-emphasis-600);
}

.blueskyCommentText {
  margin: 0.3rem 0 0;
}

.blueskyCommentLikes::before {
  content: "❤️\a0";
}

.blueskyCommentLikes::after {
  content: " ";
}

.blueskyCommentReposts::before {
  content: "🔁\a0";
}

.blueskyCommentReposts::after {
  content: " ";
}

.blueskyCommentLink::before {
  content: "→\a0";
}

.blueskyCommentLink::after {
  content: " ";
}

.blueskyCommentLink {
  margin-right: 0.25rem;
  display: inline-flex;
  align-items: center;
  color: var(--ifm-color-emphasis-600);
  text-decoration: none;
  margin-top: 0.25rem;
}

img.blueskyCommentEmbedThumb {
  max-height: 250px;
}

.blueskyCommentImages {
  margin-top: 0.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.blueskyCommentImage {
  max-width: 100%;
  height: auto;
  border-radius: 0.25rem;
  border: 1px solid var(--ifm-color-emphasis-300);
}

.blueskyNoCommentYet {
  color: var(--ifm-color-emphasis-600);
  font-size: 1rem;
  line-height: 1.5;
  text-align: center;
  padding: 20px;
  background-color: var(--ifm-color-emphasis-100);
  border: 1px solid var(--ifm-color-emphasis-300);
  border-radius: 8px;
  margin: 20px 0;
}

.blueskyNoCommentYetCTA {
  font-weight: bold;
  color: var(--ifm-color-primary-dark);
  cursor: pointer;
  text-decoration: underline;
}

[data-theme="dark"] .blueskyNoCommentYetCTA {
  color: var(--ifm-color-primary-light);
}

.blueskyPostLikes {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.blueskyLikersAvatars {
  display: inline-flex;
  align-items: center;
  margin-left: 0.4rem;
}

.blueskyLikersAvatarLink {
  margin-left: -1rem;
  line-height: 0;
}

.blueskyLikersAvatarLink:first-child {
  margin-left: 0;
}

.blueskyLikersAvatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 2px solid var(--ifm-background-color);
  display: block;
}

.blueskyLikersAvatarMore {
  margin-left: 0.4rem;
  font-size: smaller;
  color: var(--ifm-color-emphasis-600);
}

.blueskyEngagementRow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}

.blueskyButton {
  align-items: center;
  background-color: var(--ifm-color-emphasis-200);
  border-radius: 6px;
  border: 1px solid var(--ifm-color-emphasis-300);
  box-shadow: none;
  color: var(--ifm-font-color-base);
  display: inline-flex;
  font-size: 0.9rem;
  font-weight: 400;
  gap: 0.4rem;
  margin-bottom: 0.8rem;
  margin-right: 0.4rem;
  padding: 0.4rem 0.9rem;
  text-decoration: none;
  transition:
    background-color 0.2s ease,
    color 0.2s ease;
}

.blueskyButton:hover {
  background-color: var(--ifm-color-primary);
  border-color: var(--ifm-color-primary);
  color: white;
}
```

Dans un prochain article, nous apprendrons à interagir avec Bluesky :

* Proposer un bouton sous l'article pour sauter vers les posts Bluesky afin que vos visiteurs puissent :
  * Liker votre article (ce qui est toujours agréable, non ?),
  * Partager votre article (encore mieux) et
  * Lancer ou participer à une discussion
* Nous afficherons aussi le nombre de likes et de reposts sur le post Bluesky et
* Récupérer tous les commentaires publiés sur Bluesky et les afficher sur votre blog ; cela augmentera sans aucun doute l'engagement de vos visiteurs.

## Remarques finales - Attention à la commande swizzle {#final-remarks---take-care-about-the-swizzle-command}

Vous devez comprendre qu'en swizzlant (surchargeant) le layout BlogPostItem, vous n'êtes plus *aligné* avec le layout standard de Docusaurus.

**Si, dans une prochaine release, de nouvelles fonctionnalités sont ajoutées par Docusaurus, vous ne les aurez pas !** puisque vous n'utilisez plus le layout par défaut pour vos articles.

Gardez-le simplement en tête et, peut-être, de temps en temps (après une release majeure par exemple), pensez à relancer la commande swizzle et à refaire votre personnalisation.

C'est pour cette raison que nos modifications du fichier index.js ont été réduites au minimum.
