Aller au contenu principal

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 (ou GET /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 : APIReference pour les endpoints, TechArticle pour les pages de prose et les scénarios, WebSite sur 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 :

api-doc-config
{ "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/
OptionRôle
--configle config JSON que la page hôte inline dans #api-doc-config
--site-urlURL absolue de la page de documentation déployée
--outrépertoire où l’arbre statique est écrit
--languagecatalogue 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 :

out/
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.json

Chaque 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 contentId vit 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 ni robots.txt ni index.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.