Quand vos visiteurs ne lisent plus vos pages, ils les analysent
Une part croissante du trafic web ne provient plus d'humains. Les LLMs (Large Language Models) — ceux qui propulsent les assistants IA, les moteurs de recherche intelligents et les agents autonomes — fetchez vos pages HTML pour en extraire le contenu utile. Et ce qu'ils reçoivent, c'est souvent 60 Ko de balisage pour quelques centaines de mots d'information réelle : classes Bootstrap, icônes SVG, bandeau cookie, footer… autant de tokens gaspillés que l'agent tokenise et ignore.
Cloudflare a identifié ce problème en février 2025 avec sa fonctionnalité Markdown for Agents : lorsqu'un client envoie l'en-tête Accept: text/markdown, leur edge convertit la réponse HTML en markdown à la volée, réduisant le volume de tokens d'environ 80 %. Pratique — mais réservé aux plans Pro et au-dessus.
Bonne nouvelle : il n'y a aucune magie propriétaire là-dedans. C'est de la négociation de contenu HTTP, un mécanisme standardisé depuis les débuts du web. Et si votre application tourne sur Symfony, vous pouvez reproduire ce comportement en une après-midi, avec un seul Event Subscriber et un package Composer.
La négociation de contenu HTTP : le contrat avant le code
Le principe est simple : une même URL sert plusieurs représentations d'une ressource selon ce que le client déclare accepter via l'en-tête Accept.
GET / HTTP/1.1
Accept: text/html → réponse HTML classique
GET / HTTP/1.1
Accept: text/markdown → réponse en markdown
Pas de /md/ dans l'URL, pas de route dédiée. Le RFC 7231 définit ce mécanisme depuis 2014. C'est le navigateur qui envoie text/html par défaut ; les agents IA, eux, peuvent envoyer text/markdown pour signaler leur intention d'obtenir une version épurée.
Trois détails techniques méritent attention avant d'implémenter :
- Le type MIME
text/markdownn'est pas encore standardisé de façon définitive — le RFC 7763 le définit, mais certains agents envoienttext/plainou des variantes. Votre implémentation doit être tolérante. - La réponse markdown doit porter le bon
Content-Typepour éviter que les proxies ou CDN ne la mettent en cache comme du HTML. - L'en-tête
Vary: Acceptest indispensable : il indique aux caches (navigateurs, Varnish, CDN) que la réponse varie selon cet en-tête, évitant de servir du markdown à un navigateur ou du HTML à un agent.
Implémentation Symfony : un Event Subscriber suffit
L'architecture Symfony s'y prête parfaitement. Un Event Subscriber écoutant KernelEvents::RESPONSE intercepte chaque réponse et la transforme si nécessaire.
Dépendance Composer
Pour la conversion HTML → Markdown, le package league/html-to-markdown est la référence :
composer require league/html-to-markdown
Le Subscriber
<?php
namespace App\EventSubscriber;
use League\HTMLToMarkdown\HtmlConverter;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class MarkdownForAgentsSubscriber implements EventSubscriberInterface
{
public function __construct(private HtmlConverter $converter) {}
public static function getSubscribedEvents(): array
{
return [KernelEvents::RESPONSE => 'onKernelResponse'];
}
public function onKernelResponse(ResponseEvent $event): void
{
$request = $event->getRequest();
$response = $event->getResponse();
// Vérifier si le client accepte markdown
$accept = $request->headers->get('Accept', '');
if (!str_contains($accept, 'text/markdown')) {
return;
}
// Ne transformer que les réponses HTML
if (!str_contains($response->headers->get('Content-Type', ''), 'text/html')) {
return;
}
$html = $response->getContent();
$markdown = $this->converter->convert($html);
$response->setContent($markdown);
$response->headers->set('Content-Type', 'text/markdown; charset=utf-8');
// Crucial : indiquer aux caches que la réponse varie selon Accept
$response->setVary('Accept', false);
}
}
Déclarer le service
Si l'autowiring est activé (c'est le cas par défaut dans Symfony), le subscriber est détecté automatiquement. Sinon, ajoutez-le dans services.yaml :
App\EventSubscriber\MarkdownForAgentsSubscriber:
tags: [kernel.event_subscriber]
Les trois pièges à éviter en production
1. L'en-tête Vary oublié
Sans Vary: Accept, un reverse proxy (Varnish, Nginx, votre CDN) peut mettre en cache la version markdown et la servir à tous les visiteurs suivants — y compris les humains avec leurs navigateurs. Résultat : une page incompréhensible. Toujours ajouter cet en-tête.
2. La qualité de la conversion HTML → Markdown
Les pages HTML modernes contiennent des éléments inutiles pour un agent : navigation, footer, scripts, styles inline. Configurez HtmlConverter pour les exclure :
$converter = new HtmlConverter([
'strip_tags' => true,
'suppress_errors' => true,
]);
Considérez aussi cibler uniquement le contenu principal (balise <main> ou <article>) avant conversion, pour maximiser le ratio signal/bruit.
3. Ne pas transformer les réponses d'API ou les assets
Votre subscriber doit s'assurer de ne traiter que les réponses HTML de pages entières. Vérifiez le Content-Type de la réponse avant toute transformation, et ignorez les requêtes XHR ou les endpoints JSON.
Ce que ça change concrètement
Pour une page type de 1000 mots dans une application Symfony avec Bootstrap et quelques composants UI, le gain est significatif :
| Format | Taille approximative | Tokens estimés |
|---|---|---|
| HTML complet | ~60 Ko | ~15 000 tokens |
| Markdown extrait | ~8 Ko | ~2 000 tokens |
Un ratio de 7:1 qui se traduit directement en coûts réduits pour les agents qui consomment vos contenus, et en une meilleure qualité de réponse puisqu'il y a moins de bruit à filtrer.
Au-delà de l'économie de tokens, c'est aussi un signal positif pour les moteurs de recherche IA et les crawlers nouvelle génération qui privilégient les sources faciles à parser.
Conclusion
Ce que Cloudflare vend comme une fonctionnalité Pro n'est, en réalité, qu'une bonne application d'un standard HTTP vieux de trente ans. Symfony vous donne tous les outils pour l'implémenter proprement : un Event Subscriber, une dépendance Composer légère, et quelques dizaines de lignes de code.
L'article original de Tom Girou sur DEV.to détaille l'implémentation complète avec les tests associés — une lecture complémentaire recommandée si vous souhaitez embarquer cette feature en production avec une couverture de tests solide.
Avant d'ouvrir votre carte bleue pour un plugin ou un plan CDN premium, vérifiez ce que votre framework gère nativement. Dans l'écosystème Symfony/PHP, la réponse est souvent : déjà tout.