Rendre la documentation trouvable.
ApiGlow se rend dans le navigateur, sous des routes à fragment. Les crawlers s’en accommodent mal. Les crawlers d’IA — GPTBot, ClaudeBot, PerplexityBot — récupèrent le HTML et n’exécutent aucun JavaScript. Google, lui, exécute le JavaScript mais traite chaque route #/… comme la même URL : il indexe la vue d’accueil et rien en dessous.
Deux mécanismes répondent à ça : l’en-tête de document que chaque installation gère à l’exécution, et le miroir statique que le CLI apiglow bake écrit sur disque.
Ce que fait chaque installation
L’app met à jour l’en-tête du document à chaque changement de route, sans 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 est propriétaire du titre.- Meta description — du texte brut dérivé du contenu de la route, plafonné autour de 160 caractères. Créée si la page hôte n’en a pas, mise à jour sur place sinon.
- JSON-LD — un bloc par route :
APIReferencepour les endpoints,TechArticlepour les pages de prose et les scénarios,WebSitesur l’accueil. Les routes qu’aucun type ne décrit, comme l’audit et la page premier appel, n’en émettent pas.
Il n’y a 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 désignerait la même adresse à chaque fois. Les canonicals existent dans les instantanés bakés.
Rester hors de l’index
seo.index: false injecte <meta name="robots" content="noindex"> avant le premier rendu :
{ "seo": { "index": false } }La clé est racine seulement en multi-spec : une page est servie à une URL, et un crawler lit ce HTML sans choisir de spec.
Pour couvrir les fichiers que la balise meta n’atteint pas — le schéma lui-même, les miroirs .md bakés — envoyez pour eux un en-tête de réponse X-Robots-Tag: noindex.
Un noindex 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.
Le bake : un miroir statique en une commande
apiglow bake est un CLI compagnon destiné à l’auteur de la doc. Il est livré dans le paquet npm ("bin": { "apiglow": "dist/cli.js" }, Node ≥ 24 — le même binaire lance apiglow audit en CI) 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, aucun script, et un lien de retour vers l’app interactive. À côté se trouve un miroir Markdown, produit par le même générateur que le « Copy page » de l’app, avec les {{var}} laissés littéraux. 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 et pointe vers chaque instantané. 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. Quand un instantané et l’app divergent, 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 qu’un agent ne peut pas 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.
Le lancer en CI
Le bake résout ses deux familles d’adresses différemment. Les fichiers que le config nomme sont lus depuis le disque, relativement au fichier de config — / initial compris, puisque seule une URL avec schéma est récupérée en ligne. Chaque URL écrite dans un fichier généré se résout contre --site-url. C’est ce qui permet de baker avant que quoi que ce soit ne soit déployé.
Ce que le bake dégrade, et ce qui l’arrête
- Un schéma qui ne se charge pas arrête le bake : 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. - Le HTML brut dans le Markdown est échappé plutôt que rendu dans les instantanés : il n’y a pas d’assainisseur DOM dans Node. Une page bakée peut donc se rendre moins richement que l’app.
- Un scénario dont le schéma est inline (
openapi.spec) n’a pas de recette Arazzo : elle nommerait une source qu’aucun runner ne peut récupérer. C’est averti, pas silencieux. - L’overlay du lecteur n’est jamais baké.
- Le sitemap ne porte pas de
<lastmod>: le générateur n’a pas d’horloge, et une date de bake décrirait le build plutôt que la documentation. - Le bake n’émet ni
robots.txtniindex.html. Ceux-là restent les vôtres.
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 le site.