Liens Enrichis pour Développeurs Paresseux

Quand je partage un lien de blog sur Twitter ou Slack, il apparaît comme du texte brut. Pas d’image d’aperçu. Juste une URL comme granda.org/en/2026/01/02/claude-code-on-the-go/.

J’avais besoin d’images Open Graph. L’approche standard : créer manuellement une image 1200x630 pour chaque article. C’est fastidieux. J’ai demandé à Claude de l’automatiser.

flowchart LR
    Push[git push] --> GHA[GitHub Actions]
    GHA --> Hugo[Hugo Server]
    GHA --> PW[Playwright]
    PW -->|screenshot| Hugo
    PW --> IMG[OG Image]
    IMG --> Commit[git commit]
    Commit --> Deploy[Deploy]
flowchart TB
    Push[git push] --> GHA[GitHub Actions]
    GHA --> Hugo[Hugo Server]
    GHA --> PW[Playwright]
    PW -->|screenshot| Hugo
    PW --> IMG[OG Image]
    IMG --> Commit[git commit]
    Commit --> Deploy[Deploy]

La Configuration

J’ai expliqué le problème à Claude : les articles ont besoin d’images d’aperçu social, mais je ne veux pas les créer manuellement. Capture d’écran du contenu de l’article, enregistrement en tant qu’image OG, mise à jour automatique du frontmatter.

Ce Que Claude A Construit

ComposantObjectif
generate-og-image.jsScript Playwright pour capturer les articles
generate-og-images.ymlGitHub Action déclenchée lors des modifications d’articles
Modifications de baseof.htmlBalises meta og:image conditionnelles

Le script Playwright lance Chrome headless, navigue vers l’article, force le mode clair, masque l’en-tête et le pied de page, et capture l’écran à 1200x630 :

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
  viewport: { width: 1200, height: 630 },
  deviceScaleFactor: 2, // Retina for crisp text
});

// Force light mode, hide nav
await page.evaluate(() => {
  document.documentElement.setAttribute("data-theme", "light");
  document.querySelector("header").style.display = "none";
  document.querySelector("footer").style.display = "none";
});

await page.screenshot({ path: outputPath, type: "png" });

La GitHub Action se déclenche à chaque modification de fichier .en.md, démarre Hugo, exécute le script et commit l’image générée dans le dépôt :

on:
  push:
    branches: [main]
    paths:
      - "content/posts/**/*.md"

steps:
  - name: Start Hugo server
    run: hugo server --bind 0.0.0.0 --port 1313 &

  - name: Generate OG images
    run: node scripts/generate-og-image.js "$file"

  - name: Commit changes
    run: |
      git add static/images/posts/ content/posts/
      git commit -m "Generate Open Graph images for blog posts"
      git push

Le template inclut conditionnellement l’image lorsque le frontmatter a un champ image :

{{- if .Params.image -}}
<meta property="og:image" content="{{ $canonical }}{{ .Params.image }}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="{{ $canonical }}{{ .Params.image }}">
{{- else -}}
<meta name="twitter:card" content="summary">
{{- end -}}

404 : Slug Introuvable

Première exécution. Playwright a capturé une page vide. L’URL retournait 404.

Le script avait construit l’URL à partir du nom de fichier : automatic-blog-translations.en.md/en/2025/12/23/automatic-blog-translations/

Mais Hugo n’utilise pas le nom de fichier pour les slugs. Il utilise le titre. L’URL réelle était /en/2025/12/23/automatic-blog-translations-with-claude-and-github-actions/.

J’aurais pu réimplémenter l’algorithme slugify de Hugo en JavaScript. À la place : demander à Hugo.

const output = execSync("hugo list all", { encoding: "utf8" });
// CSV output includes the exact permalink Hugo generates

Une ligne. Pas de cas particuliers. Hugo connaît déjà ses propres URLs.

La Suite

L’implémentation actuelle ne génère des images que pour les articles en anglais. Les versions traduites référencent le même chemin d’image, ce qui fonctionne, mais cela signifie que l’image OG affiche toujours du texte anglais même lors du partage d’une version espagnole ou japonaise.

Un vrai support i18n arrive dans la prochaine PR. Pour l’instant, chaque article obtient une image sociale, ce qui est mieux que rien.

La Leçon

Cela a pris 14 minutes à construire et déboguer. Maintenant chaque article obtient automatiquement une image d’aperçu social. Pas de travail manuel par article. L’automatisation est rentabilisée après une poignée d’articles.

Livrez la version fonctionnelle, découvrez les lacunes, itérez.


Articles connexes :