Skip to content
Avatar de Thomas BntThomas Bnt@thomasbnt
retour
Intégrer les composants SEO sur Discord

Intégrer les composants SEO sur Discord

Publié le
6 min de lecture · 1214 mots

Tu colles un lien vers ton site dans un salon Discord, et une petite carte apparaît toute seule : titre, description, une image. Tu n’as rien codé de spécial pour ça, c’est le comportement par défaut.

Discord vient d’ajouter une option en plus : remplacer complètement cette carte par un aperçu construit sur mesure, avec du texte formaté, une galerie d’images, des boutons.

J’ai testé ça sur ce blog, et voici comment ça marche.

Ce qu’il faut avoir en place avant de commencer :

  • Un site servi en HTTPS
  • Les balises Open Graph déjà présentes (le repli obligatoire, même avec un component embed)
  • Un rendu côté serveur (le HTML doit contenir les balises dès la réponse, pas après un script JS)
  • Le crawler Discordbot autorisé si un WAF, un CDN ou un rate-limiter est devant le site
  • Le payload JSON du component embed prêt (on y vient juste après)

Le comportement par défaut : les balises Open Graph

Sans rien ajouter, voici ce qui se passe : Discord fait une requête sur ton URL, lit le HTML renvoyé par le serveur, et cherche des balises Open Graph dans le <head>.

Si elles sont là, il en sort une carte automatiquement.

<meta property="og:title" content="Titre de la page" />
<meta property="og:description" content="Description de la page" />
<meta property="og:image" content="https://exemple.com/og-image.png" />
<meta property="og:url" content="https://exemple.com/page" />
<meta name="twitter:card" content="summary_large_image" />

Le rendu sur Discord, à partir de ces seules balises :

Preview Discord d'un lien basée uniquement sur les balises Open Graph : titre, description et image

Retenir trois points sur ce fonctionnement, parce qu’ils comptent pour la suite aussi :

  • Les balises doivent être dans le HTML envoyé par le serveur. Discord n’exécute aucun JavaScript, une balise ajoutée après coup par du JS côté client n’existe pas pour lui.
  • Le crawler s’identifie avec le user-agent Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com). Si un WAF ou un rate-limiter bloque les bots, il faut explicitement laisser passer Discordbot.
  • La preview est mise en cache environ 30 minutes. Pour forcer un rafraîchissement en test, ajouter un ?v=2 dans l’URL suffit.

Je détaille ces balises Open Graph (tailles d’image, balises Twitter Card…) dans Quelques tips pour améliorer son SEO, si tu veux creuser ce côté-là.

Le nouveau format : les component embeds

Si aucun component embed n’est présent sur la page, Discord n’affiche rien de plus que la carte du dessus.

C’est un ajout, pas un remplacement obligatoire : les balises og:* restent nécessaires dans tous les cas. Elles servent de repli si l’embed ne peut pas s’afficher (contexte qui ne le supporte pas, payload invalide, etc).

Le fonctionnement, côté crawler, est le même que pour la carte classique, avec une étape en plus :

  1. Un lien vers ta page est posté dans un message Discord.
  2. Discord fetch l’URL et lit le HTML.
  3. Il cherche en plus une balise de component embed. Si elle existe, il parse le JSON et va chercher les métadonnées de chaque image du payload.
  4. Si ce payload est valide, il remplace la carte Open Graph par ce rendu custom. Sinon, retour à la case précédente.

Deux façons d’exposer ce JSON :

Option 1, inline, un <script> dans le <head> :

<script id="discord:component-embed" type="application/json">
  {
    "component": {
      "type": 17,
      "components": [
        {
          "type": 10,
          "content": "# Patch Notes\n- Correction d'un bug d'affichage"
        }
      ]
    }
  }
</script>

Option 2, en JSON externe, avec un lien qui pointe vers un fichier sur le même domaine (ou un sous-domaine) :

<link
  rel="discord:component-embed"
  type="application/json"
  href="https://exemple.com/embeds/article.json"
/>

Cette option a une limite stricte : 3000 octets sur le fichier brut. Pour un embed avec galerie d’images et boutons, l’inline passe plus large.

Le component racine doit être un Container (type: 17).

Il peut contenir jusqu’à 40 composants au total, parmi ceux du tableau ci-dessous. N’importe quel autre type de composant invalide tout le payload :

TypeComposantNotes
1Action Rowligne de boutons
2Buttonuniquement style lien (style: 5), avec label et/ou emoji
9Sectiontexte + un accessoire (thumbnail ou bouton)
10Text Displaymarkdown Discord (titres, gras, listes, liens…)
11Thumbnailpetite image accolée à une Section
12Media Galleryjusqu’à plusieurs images/vidéos, en grand format
14Separatortrait de séparation
17Containerle composant racine

Pour les images (Thumbnail et Media Gallery), on ne fournit que l’URL, Discord se charge de récupérer les dimensions et le type de fichier :

{ "url": "https://exemple.com/images/hero.webp" }

Formats acceptés : PNG, GIF, JPEG, WebP, AVIF, et pour la Media Gallery, MP4/MOV/WebM en plus.

Ce que j’ai mis en place sur ce blog

Sur chaque article de ce blog, en plus des balises Open Graph classiques (toujours là en repli), j’envoie maintenant un component embed.

Il est construit à partir des mêmes données que la carte standard : le titre, la description, l’image de couverture, et la couleur d’accent de l’article.

La structure ressemble à ça, simplifiée :

{
  "component": {
    "type": 17,
    "accent_color": 5793266,
    "components": [
      {
        "type": 10,
        "content": "# [Titre de l'article](https://thomasbnt.dev/blog/...)\nSa description."
      },
      {
        "type": 12,
        "items": [{ "media": { "url": "https://thomasbnt.dev/.../index.png" } }]
      },
      {
        "type": 1,
        "components": [
          {
            "type": 2,
            "style": 5,
            "url": "https://thomasbnt.dev/blog/...",
            "label": "Lire l'article"
          },
          {
            "type": 2,
            "style": 5,
            "url": "https://thomasbnt.dev/blog/",
            "label": "Blog"
          }
        ]
      }
    ]
  }
}

Le rendu final sur Discord :

Aperçu du component embed de cet article dans Discord, avec l'image en grand format et les deux boutons "Lire l'article" et "Blog"

Quelques choix faits en le construisant :

  • Media Gallery plutôt que Thumbnail : au départ j’étais parti sur une Section + Thumbnail, mais l’image ressort minuscule à côté du texte. La Media Gallery affiche l’image en grand, comme une vraie bannière.
  • Pas de Separator : ça cassait visuellement l’embed pour rien entre le texte et l’image.
  • Deux boutons : un pour lire l’article, un pour revenir au blog. Simple, mais ça donne un point d’entrée en plus directement depuis Discord.
  • accent_color : c’est un entier, pas une couleur hexadécimale. Ce blog a déjà une couleur associée à chaque article (backgroundColor dans le frontmatter). Je la convertis simplement en décimal (parseInt(hex.replace("#", ""), 16)) plutôt que d’en gérer une nouvelle.

Comme pour les balises OG, ce script est généré côté serveur au moment du build (le site est en Astro, tout est statique), donc rien à exécuter côté client.

Pièges à éviter

  • Un composant hors liste, ou une clé en trop sur un bouton (id, custom_id…) invalide tout le payload, pas juste le composant fautif.
  • Retirer les balises OG en pensant qu’elles ne servent plus. Elles restent indispensables : c’est le repli utilisé partout où l’embed ne s’affiche pas.
  • Le cache de 30 minutes. En plein test, un ?v=2 sur l’URL évite de se demander pendant dix minutes pourquoi rien ne change.
  • Le budget de 10 secondes. Tout le fetch, images comprises, doit être terminé dans ce délai. Avec une Media Gallery qui charge plusieurs images, c’est plus vite atteint qu’avec une simple og:image.

Comment tester

Le Embed DebuggerFavicon de discord.com de Discord montre exactement comment le crawler lit une URL : quelles balises il a trouvées, et quel format d’embed il a choisi. C’est le premier réflexe avant de chercher un bug ailleurs.

Conclusion

Pour résumer la logique : sans rien faire, Discord affiche déjà une carte à partir des balises og:*.

Ajouter un component embed est une option en plus, pas un remplacement. Il ne s’active que si le payload JSON est présent et valide, sinon Discord retombe automatiquement sur cette carte par défaut.

Sur ce blog, ça reste léger : même titre, même description, même image que la carte classique, juste présentés différemment avec un accès direct au reste du blog.

Ressources