Trouvable par les crawlers, lisible par les agents.
ApiGlow se rend à l’exécution, dans le navigateur, sous des routes à fragment — précisément ce que les crawlers qui comptent ne savent pas lire. Les crawlers d’IA (GPTBot, ClaudeBot, PerplexityBot) récupèrent le HTML mais n’exécutent aucun JavaScript ; Google exécute le JavaScript mais traite chaque route #/… comme la même URL : il peut indexer la vue d’accueil, jamais une page d’opération. La réponse a deux moitiés : ce que chaque installation fait déjà à l’exécution, et ce que le CLI apiglow bake écrit sur disque.
Ce que chaque installation fait déjà
L’app gère l’en-tête du document à chaque changement de route, sans aucune configuration :
<title>— « {route} — {titre de l’API} » : le résumé de l’opération (ouGET /chemin), le titre de la page de prose, le nom du workflow. Une fois démarrée, l’app possède le titre.- Meta description — créée si la page hôte n’en a pas, mise à jour sinon : du texte brut dérivé du contenu de la route, plafonné autour de 160 caractères.
- JSON-LD — un bloc par route :
APIReferencepour les endpoints,TechArticlepour les pages de prose et les scénarios,WebSitesur l’accueil. Les vues qu’aucun type ne décrit (l’audit, la page premier appel) n’émettent rien — un type faux est pire que le silence.
Il n’y a délibérément pas de canonical à l’exécution : sous routage à fragment, toutes les routes partagent une seule URL serveur, et les crawlers tronquent le fragment — un canonical par route ne voudrait rien dire. Les canonicals vivent dans les instantanés bakés.
Rester hors de l’index
Certaines documentations ne doivent pas être trouvées. Une clé suffit :
{ "seo": { "index": false } }L’app injecte <meta name="robots" content="noindex"> avant le premier rendu. La clé est racine seulement en multi-spec — une page est servie à une URL, et un crawler lit ce HTML sans jamais choisir de spec. Deux notes de déploiement issues des commentaires de la config elle-même : accompagnez-la d’un en-tête X-Robots-Tag: noindex pour couvrir les fichiers non HTML (le schéma lui-même, les miroirs .md bakés), et n’ajoutez pas de Disallow dans robots.txt — une URL bloquée n’est jamais récupérée, donc le noindex n’est jamais vu. Et c’est une requête adressée aux crawlers de bonne volonté, pas une protection : une documentation qui ne doit pas être lue exige un mur d’authentification, pas une balise meta.
Le bake : un miroir statique en une commande
apiglow bake est un CLI compagnon, lancé par l’auteur de la doc — jamais par le lecteur. Il vit dans le paquet npm ("bin": { "apiglow": "dist/bake.js" }, Node ≥ 24) et lit le même config JSON que votre page hôte inline :
apiglow bake --config apidoc.config.json --site-url https://docs.example.com/ --out public/| Option | Rôle |
|---|---|
--config | le config JSON que la page hôte inline dans #api-doc-config |
--site-url | URL absolue de la page de documentation déployée |
--out | répertoire où l’arbre statique est écrit |
--language | catalogue utilisé pour le chrome des instantanés — en ou fr, en par défaut |
Il écrit un arbre de fichiers, déployé à côté de la page de l’app par ce qui déploie déjà votre site :
sitemap.xml
llms.txt
llms-full.txt
overview.html
op/{operationId}.html op/{operationId}.md
page/{slug}.html page/{slug}.md
scenario/{scenarioId}.html scenario/{scenarioId}.md
scenario/{scenarioId}.arazzo.jsonChaque route reçoit un instantané HTML — canonical vers lui-même, un <link rel="alternate" type="text/markdown"> vers son jumeau, du JSON-LD, zéro script, et un lien bien visible vers l’app interactive (« Ouvrir dans la documentation interactive »). Pas de redirection, pas d’hydratation : l’instantané est du contenu statique honnête, pas un trampoline de cloaking. À côté, un miroir Markdown — le même générateur que le « Copy page » de l’app, {{var}} laissé littéral. Chaque scénario publie en plus sa recette Arazzo, le fichier qu’un runner CI exécute tel quel. overview.html est la porte d’entrée du crawler : un lien vers chaque instantané, pour qu’atterrir sur le site ne signifie jamais un sitemap et rien d’autre. En multi-spec, tout se range sous s/{specId}/… et les trois fichiers racine couvrent toutes les specs.
Ce n’est ni un prerender ni de la génération de site statique — aucun DOM ne tourne dans Node, et l’installation du lecteur reste une balise script sans étape de build. C’est la couche d’export de l’app, écrite sur disque : si un instantané et l’app divergent un jour, c’est le générateur qu’on corrige.
Ce que les agents y gagnent
Sans bake, llms.txt et llms-full.txt n’existent qu’en téléchargements dans le navigateur, avec des liens en routes à fragment — rien qu’un agent puisse récupérer. Bakés, les deux sont des fichiers servis : llms.txt pointe vers les miroirs .md selon la convention llmstxt.org, et chaque section de llms-full.txt porte une ligne Source: vers sa page servie. Le guide de la surface agents détaille l’intérieur des fichiers.
Fait pour la CI
Le bake résout ses deux familles d’adresses différemment, à dessein : les fichiers que le config nomme sont lus depuis le disque, relativement au fichier de config (/ initial compris — seule une URL avec schéma est récupérée en ligne), tandis que chaque URL écrite dans un fichier généré se résout contre --site-url. C’est ce qui permet de baker en CI avant que quoi que ce soit ne soit déployé.
Ce que le bake refuse, et ce qu’il dégrade
- Un config disant
seo: { index: false }est une erreur dure — baker un site noindex est une contradiction. - Un schéma qui ne se charge pas arrête tout : le reste en dérive.
- Une page de prose portée par
contentIdvit dans du HTML de page hôte qu’aucun process Node ne voit : elle est nommée dans les avertissements et retirée de la carte, du sitemap et de l’arbre — une entrée pointant vers un fichier que personne n’a écrit est pire qu’une entrée de moins. - Le HTML brut dans le Markdown est échappé, pas rendu dans les instantanés (pas d’assainisseur DOM dans Node) : une page bakée peut se rendre moins richement que l’app. Dégradation documentée, pas un bug.
- Un scénario dont le schéma est inline (
openapi.spec) n’a pas de recette Arazzo — une recette générée nommerait une source qu’aucun runner ne peut récupérer. Averti, jamais silencieux. - L’overlay du lecteur n’est jamais baké — ce qui vit dans le navigateur d’un lecteur y reste.
- Le sitemap ne porte pas de
<lastmod>(le générateur n’a pas d’horloge, et une date de bake mentirait sur le moment où la documentation a changé), et le bake n’émet nirobots.txtniindex.html— ceux-là restent au site.
Relancez le bake quand le schéma, le config, les pages de prose ou les scénarios changent — en pratique : depuis le job de CI qui déploie déjà le site.