Edge SEO avec Cloudflare Workers

Corrigez et optimisez le SEO à la périphérie du CDN — injectez des balises, redirigez à grande échelle et corrigez un CMS que vous ne pouvez pas modifier.

📖 12 min de lecture 🕑 Mis à jour 2026-06-22

L’edge SEO consiste à modifier ce qu’un navigateur ou un robot d’exploration reçoit après que votre serveur d’origine a produit la page, mais avant qu’elle n’atteigne le client. La réécriture se produit sur le CDN — dans votre cas, sur un Cloudflare Worker placé entre le visiteur et votre site.

Voyez cela comme un correcteur posté au bureau de poste. L’auteur (votre CMS) a déjà rédigé et cacheté la lettre. Vous ne pouvez pas lui faire tout réécrire — peut-être tourne-t-il sur une plateforme héritée, peut-être une autre équipe possède-t-elle les templates, peut-être le changement est-il urgent. Le correcteur ouvre donc l’enveloppe au tout dernier moment, corrige une faute sur la balise canonical, appose une redirection sur une URL morte, et la fait suivre. Le destinataire ignore que la lettre a été touchée.

Comme ce site tourne déjà sur Cloudflare Pages, vous êtes à un Worker de cette capacité. Vous n’avez pas besoin d’un nouveau prestataire, d’un gestionnaire de balises, ni d’un accès à l’origine. Il vous faut un handler fetch et HTMLRewriter, le parseur HTML en streaming de Cloudflare. La suite de ce guide vous montre quand c’est le bon choix, les patterns à connaître, un exemple complet, et les façons dont cela peut discrètement mal tourner.

Quand l’utiliser

L’edge SEO est un outil de précision, pas un réflexe par défaut. Tournez-vous vers lui quand le chemin habituel — modifier les templates à l’origine — est bloqué ou trop lent.

SituationPourquoi l’edge l’emporte
CMS hérité ou verrouilléLa plateforme génère le <head> et vous ne pouvez pas toucher au template. L’edge réécrit la sortie quel que soit le backend.
Plateforme SaaS / hébergéeShopify, un builder de site marketing, une vitrine headless que vous ne maîtrisez pas de bout en bout. Aucun serveur où se connecter en SSH — mais un Worker peut se placer en amont.
Cohérence à l’échelle du siteVous avez besoin de la même logique canonical ou hreflang sur 50 000 URL. Un seul Worker l’applique ; vous ne faites pas confiance à 50 000 templates pour s’accorder.
Correctif urgentUn mauvais déploiement a expédié un noindex en production. Le prochain sprint dev est dans deux semaines. Un correctif Worker est en ligne en 60 secondes et réversible.
Expériences SEO A/BTestez un pattern de balise title sur 50 % du trafic d’une section sans forker les templates.

🧑‍💻 Vue développeur : l’edge est la bonne couche quand le changement est transversal (s’applique à de nombreuses pages par règle) ou critique en temps (le déploiement à l’origine est le goulot d’étranglement). Si un changement est réellement spécifique à une page et que vous contrôlez le template, faites-le à l’origine — c’est là sa place et c’est là que le prochain ingénieur le cherchera.

Le revers : chaque règle edge est un morceau de logique qui vit en dehors du flux de revue habituel de votre dépôt. Traitez-la comme de l’infrastructure de production, pas comme un post-it. Nous y reviendrons dans les Mises en garde.

Patterns courants

Ce sont les chevaux de trait. Chacun ne représente que quelques lignes de HTMLRewriter, qui diffuse la réponse en streaming et vous laisse attacher des handlers à des sélecteurs CSS — vous ne mettez ainsi jamais le document entier en mémoire tampon.

Injecter ou corriger canonical et hreflang

La tâche d’edge SEO la plus courante. Un CMS émet un canonical pointant vers le mauvais hôte (http, un domaine de staging, une incohérence de slash final), ou omet entièrement hreflang sur un site bilingue.

class CanonicalFixer {
  constructor(url) {
    this.canonical = `https://example.com${new URL(url).pathname}`;
    this.found = false;
  }
  element(el) {
    // Rewrite an existing canonical to the correct absolute URL.
    el.setAttribute("href", this.canonical);
    this.found = true;
  }
}

Vous associez cela à un handler end sur <head> pour injecter la balise lorsqu’elle manquait — illustré intégralement dans l’exemple détaillé ci-dessous.

Rediriger à grande échelle (301 / 302)

Les migrations laissent derrière elles des milliers d’URL mortes. Au lieu d’alourdir la config de l’origine ou _redirects, pilotez-les depuis un Worker adossé à une map (ou KV pour de grands ensembles).

const REDIRECTS = {
  "/old-pricing": "/pricing",
  "/blog/2019/seo-tips": "/guides/seo-basics",
};

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const target = REDIRECTS[url.pathname];
    if (target) {
      return Response.redirect(`${url.origin}${target}`, 301);
    }
    return fetch(request); // pass through to origin
  },
};

💡 Astuce : pour des dizaines de milliers de redirections, déplacez la map dans Workers KV et faites la recherche par clé. Les lectures KV sont mises en cache à la périphérie, donc la recherche reste rapide et vous évitez d’expédier un énorme objet à chaque démarrage à froid.

Corriger les balises meta et les données structurées

Ajoutez une meta description manquante, retirez un noindex parasite, ou injectez du JSON-LD que le CMS ne sait pas produire. Injecter des données structurées revient simplement à ajouter un <script type="application/ld+json"> au <head> :

class JsonLdInjector {
  constructor(data) {
    this.json = JSON.stringify(data);
  }
  element(head) {
    head.append(
      `<script type="application/ld+json">${this.json}</script>`,
      { html: true }
    );
  }
}

Expériences SEO A/B

Répartissez le trafic de manière déterministe (par un cookie haché ou un bucket de chemin), servez un title/description variant à un bucket, et mesurez la différence d’impressions et de CTR dans Search Console. Gardez le contenu identique pour les utilisateurs — vous testez le snippet affiché dans la SERP, pas du cloaking de pages différentes.

Traitement adapté aux robots via User-Agent

Détectez Googlebot ou Bingbot et appliquez des règles — par exemple servir un fallback entièrement rendu, ou ajouter des en-têtes de cache ajustés pour les robots. C’est légitime uniquement lorsque le robot et l’utilisateur reçoivent le même contenu significatif. Faire diverger le contenu lui-même, c’est du cloaking ; voir les Mises en garde.

const ua = request.headers.get("user-agent") || "";
const isBot = /googlebot|bingbot|duckduckbot/i.test(ua);

Un exemple détaillé

Voici un Worker complet, prêt pour la production, qui accomplit la chose la plus précieuse pour un site bilingue comme celui-ci : il garantit un canonical correct et un jeu hreflang correspondant sur chaque page — corrigeant la balise si elle est présente, l’injectant si elle est absente. Il est idempotent (l’exécuter deux fois ne change rien) et il laisse passer tout le reste intact.

const SITE = "https://example.com";

// Map a path to its language alternates. Adapt to your routing.
function alternatesFor(pathname) {
  const en = pathname.startsWith("/zh/")
    ? pathname.replace(/^\/zh\//, "/en/")
    : pathname;
  const zh = pathname.startsWith("/en/")
    ? pathname.replace(/^\/en\//, "/zh/")
    : pathname;
  return [
    { lang: "en", href: `${SITE}${en}` },
    { lang: "zh", href: `${SITE}${zh}` },
    { lang: "x-default", href: `${SITE}${en}` },
  ];
}

// Rewrite an existing canonical to the canonical absolute URL.
class CanonicalRewriter {
  constructor(canonical) {
    this.canonical = canonical;
    this.seen = false;
  }
  element(el) {
    el.setAttribute("href", this.canonical);
    this.seen = true;
  }
}

// On </head>, inject anything the page was missing.
class HeadCloser {
  constructor(canonical, alternates, rewriter) {
    this.canonical = canonical;
    this.alternates = alternates;
    this.rewriter = rewriter;
  }
  element(head) {
    if (!this.rewriter.seen) {
      head.append(
        `<link rel="canonical" href="${this.canonical}">`,
        { html: true }
      );
    }
    // Always (re)assert hreflang. We strip old ones first (below),
    // so appending here yields exactly one correct set.
    for (const alt of this.alternates) {
      head.append(
        `<link rel="alternate" hreflang="${alt.lang}" href="${alt.href}">`,
        { html: true }
      );
    }
  }
}

export default {
  async fetch(request) {
    const response = await fetch(request);

    // Only rewrite HTML; leave assets, JSON, redirects untouched.
    const type = response.headers.get("content-type") || "";
    if (!type.includes("text/html")) return response;

    const url = new URL(request.url);
    const canonical = `${SITE}${url.pathname}`;
    const alternates = alternatesFor(url.pathname);
    const rewriter = new CanonicalRewriter(canonical);

    return new HTMLRewriter()
      // Remove any stale hreflang so we don't duplicate them.
      .on('link[rel="alternate"][hreflang]', { element: (el) => el.remove() })
      .on('link[rel="canonical"]', rewriter)
      .on("head", new HeadCloser(canonical, alternates, rewriter))
      .transform(response);
  },
};

Ce qui rend ce Worker sûr à expédier :

  • Il est cadré par type de contenu. Les réponses non HTML passent verbatim — vous ne corrompez jamais une image ou une API JSON.
  • Il est idempotent. Les balises hreflang périmées sont retirées, puis exactement un jeu correct est ajouté. Le réexécuter produit un résultat identique.
  • Il fonctionne en streaming. HTMLRewriter traite la réponse au fil de son passage ; il n’y a pas de tampon plein-document et quasiment aucun coût de latence.
  • Il corrige-ou-injecte le canonical en une seule passe : le CanonicalRewriter corrige une balise existante, et HeadCloser n’en ajoute une que si aucune n’a été vue.

Déployez-le avec Wrangler et routez-le vers votre zone :

npx wrangler deploy
# Then bind a route in wrangler.toml, e.g.
# routes = [{ pattern = "example.com/*", zone_name = "example.com" }]

🧑‍💻 Vue développeur : gardez la constante SITE et le mapping alternatesFor au même endroit et écrivez des tests unitaires pour la fonction de mapping. La logique de routage des langues est l’endroit où se cachent les bugs d’edge SEO — une regex qui gère mal /en/en/ ou la page d’accueil émettra silencieusement de mauvais alternates sur tout le site.

Mises en garde

L’edge SEO est puissant précisément parce qu’il est invisible — ce qui est aussi la façon dont il vous mord. Intégrez ces principes avant de router un Worker sur du trafic de production.

  • Rendez chaque réécriture idempotente. Si l’origine se met plus tard à émettre la balise que vous injectez, votre Worker ne doit pas produire de doublons. Le pattern ci-dessus retire-puis-ajoute exactement pour cette raison. Des canonicals dupliqués sont pires qu’aucun.
  • Ne luttez pas contre l’origine. L’edge et le CMS ne doivent pas affirmer des canonicals différents ni des directives robots contradictoires. Quand une future modification de template entre en collision avec une règle edge, Google voit des signaux contradictoires et vous obtenez une indexation non déterministe. Documentez chaque règle edge pour que l’équipe d’origine sache qu’elle existe.
  • Attention au cache. Cloudflare peut mettre en cache le HTML réécrit. Si votre réécriture dépend de la requête (User-Agent, cookie, geo), vous devez faire varier la clé de cache en conséquence, sinon vous servirez un seul variant à tout le monde. Pour une logique dépendant de la requête, définissez Cache-Control / Vary délibérément, ou contournez le cache pour ces routes.
  • Surveillez votre budget de latence et d’erreurs. Un Worker qui lève une exception sur un cas limite peut afficher une page blanche aux robots. Enveloppez la logique risquée dans un try/catch et échouez en mode ouvert (fail open) — renvoyez la response originale intacte plutôt qu’un 500.
  • Surveillez-le comme du code d’origine. Branchez wrangler tail, journalisez les compteurs de réécriture, et surveillez les rapports Pages et Suppressions de Search Console après chaque changement. Un bug d’edge se manifeste par une anomalie d’indexation, pas par une stack trace.
  • Ne l’utilisez jamais pour du cloaking. Servir aux robots un contenu différent de celui des utilisateurs — du texte bourré de mots-clés pour Googlebot, une page propre pour les humains — viole les politiques anti-spam de Google et risque une action manuelle. Le traitement basé sur le UA convient pour la livraison et le cache ; ce n’est pas un permis pour afficher deux pages différentes.

⚠️ Attention : le péché capital de l’edge SEO est la divergence silencieuse entre ce que voient les utilisateurs et ce que voient les robots. Vérifiez avec l’Inspection d’URL de Search Console (« Voir la page explorée ») que le HTML rendu obtenu par le robot correspond à celui qu’obtient un navigateur normal. S’ils diffèrent sur le fond, vous faites du cloaking — intentionnellement ou non.

Où cela s’inscrit

L’edge SEO est une tactique au sein de la couche de build plus large de votre stack — c’est ainsi que vous imposez le SEO technique lorsque le template ne le peut pas. Pour les fondations sur lesquelles il repose (rendu, stratégie canonical, maillage interne), voir la couche Build. Et une fois que votre Worker façonne les réponses, assurez-vous que vos directives d’exploration s’accordent avec lui : validez robots.txt et votre sitemap avec l’outil robots & sitemap afin que l’edge, l’origine et vos règles d’exploration racontent tous la même histoire à Google.

Points clés à retenir

  • ✅ Utilisez l’edge pour corriger le SEO après l’origine et avant le client — idéal pour les CMS verrouillés, les plateformes hébergées, les règles à l’échelle du site et les correctifs urgents.
  • HTMLRewriter diffuse la réponse en streaming, donc injecter canonical, hreflang, meta ou JSON-LD ne coûte quasiment aucune latence ni aucun tampon plein-document.
  • ✅ Rendez chaque réécriture idempotente (retirer-puis-ajouter) pour qu’elle ne duplique jamais des balises que l’origine pourrait émettre plus tard.
  • ✅ Cadrez les réécritures à text/html, enveloppez la logique dans un try/catch, et échouez en mode ouvert vers la réponse originale.
  • ✅ Traitez les Workers comme du code de production : versionnez-le, documentez chaque règle pour qu’elle ne puisse pas entrer en collision avec l’origine, et surveillez Search Console après chaque changement.
  • ✅ Ne faites jamais diverger le contenu entre utilisateurs et robots — le traitement par UA sert à la livraison, pas au cloaking.