WordloopWordloop
Decisions (ADRs)

Hosting-layer Link header for llms.txt discovery

We publish the llms-txt rel link via Firebase Hosting headers because the docs site is a Next.js static export and cannot set response headers at the framework layer.

0004 — Hosting-layer Link header for llms.txt discovery

Status: Accepted Date: 2026-04-19 Deciders: docs platform Supersedes:Superseded by:

Context

The llms.txt specification recommends that sites advertise their machine-readable index via the HTTP Link header with rel="llms-txt", in addition to serving the file at /llms.txt. This lets agents discover the index without guessing at conventional paths and without parsing HTML.

The Wordloop documentation site is built with Next.js 15 in static-export mode (output: 'export'). Static export does not support runtime middleware, route handlers that mutate response headers, or next.config.js headers() for the exported bundle — those hooks are only honoured by the Node server, which we are not running in production. Consequently, the Link header cannot be set at the framework layer.

The site is served by Firebase Hosting, which supports per-path response headers declaratively in firebase.json under hosting.headers.

Decision

Set the Link: </llms.txt>; rel="llms-txt", </llms-full.txt>; rel="llms-full-txt" header on every response from Firebase Hosting, via the firebase.json hosting.headers array. Additionally, set Content-Type: text/markdown; charset=utf-8 on every **/*.md path so the per-page markdown exports are served with the correct media type, and text/plain on the two llms*.txt files.

The header applies to all paths (source: "/**"). The rel advertisement is cheap and universally safe — every Wordloop documentation page is a valid entry point for an agent that then looks up the index.

Consequences

  • Agents following the llms.txt discovery pattern via curl -I or a HEAD request find the index without needing to hardcode /llms.txt.
  • The .md exports of each documentation page are served with the correct MIME type; command-line tooling (curl, wget) treats them as text.
  • The configuration lives in firebase.json — a hosting-platform-specific file. If we ever migrate hosting providers, this configuration has to be reimplemented in the new provider's equivalent. This is captured in the debt annotation below.

Alternatives considered

  • Set the header in a Next.js middleware. Rejected: middleware is incompatible with static export.
  • Set the header via a meta tag in <head>. Rejected: meta equivalents of the Link header (<link rel="llms-txt" href="...">) are not part of the spec and not observed by agents doing header-only HEAD requests.
  • Add an Express shim in front of the static export. Rejected: introducing a server just to set one header sacrifices the operational simplicity that motivated static export in the first place.
  • Rely on convention only (/llms.txt at the root). Rejected: the spec explicitly recommends the header. It is cheap to set and the canonical way for agents to discover the index.

Debt annotation

Principal: ~1 hour. One firebase.json edit, one ADR, one test.

Interest: Near-zero. The configuration does not drift; the header string is stable.

Multiplier: Hosting migration. If we move off Firebase, the firebase.json block has to be translated to the new hosting provider's header syntax. The content of the header does not change; only the declaration site does. If we ever move to a self-hosted Next.js runtime, the header moves to middleware and firebase.json can be discarded.

Verification

  • curl -I https://docs.wordloop.ai/docs/learn/architecture/overview shows the Link header with both llms-txt and llms-full-txt targets.
  • curl -I https://docs.wordloop.ai/docs/learn/architecture/overview.md returns Content-Type: text/markdown; charset=utf-8.
  • curl -I https://docs.wordloop.ai/llms.txt returns Content-Type: text/plain; charset=utf-8.

On this page