Serve Markdown to AI Agents
Serve TinaCMS source Markdown or MDX to AI agents when they request Accept: text/markdown. Browsers can continue to receive HTML at the same URL.
To serve both formats at one URL, route requests through a server, CDN, or edge layer that handles content negotiation. A static host alone cannot do this.
These examples use the same negotiator library as TinaDocs to select between HTML and Markdown. It respects quality values (q), wildcards, and case-insensitive media types. Markdown with q=0 is never selected. Missing headers and generic wildcards default to HTML; if neither format is acceptable, these examples fall back to HTML, matching TinaDocs.
Install the library and its TypeScript types in your project:
MDX can contain JSX and TinaCMS components. Most AI agents can still read it, but clients that require CommonMark may need a separate MDX-to-Markdown conversion step.
The examples assume each docs URL has a matching source file, so /docs/guides/setup maps to content/docs/guides/setup.mdx and /docs maps to content/docs/index.mdx. A Markdown request for a page stored anywhere else, such as a .md file or guides/index.mdx, returns a 404 even though its HTML works, so adjust the file lookup to match your content layout.
The examples also serve every file they find, including its frontmatter. If your content includes drafts or private fields, filter them out before returning the response.
Next.js App Router
Create a static route handler that returns the source MDX.
Use middleware to negotiate the response and keep the public URL unchanged. This matches TinaDocs' Next.js 15 implementation. Merge it into any existing middleware and matcher configuration. Middleware paths are relative to basePath, so if your basePath is /docs, update the matcher and the rewrite path to match.
Static exports do not run middleware. TinaDocs uses EXPORT_MODE=static to select its static-export configuration; a static host still serves HTML at the docs URL regardless of Accept.
The middleware appends Vary: Accept to both representations, but Next.js or the hosting layer can replace this header. Vercel includes Accept in its cache key automatically. For other CDNs and reverse proxies, configure separate cache entries by Accept or bypass caching for docs routes; otherwise HTML and Markdown can share a cache entry.
Astro
Use an Astro adapter for a Node.js server with on-demand rendering. Set output: "server", or set export const prerender = false on your docs pages as well as the endpoint below, so middleware can inspect each request. Make content/docs available to the deployed server: this example reads source MDX from disk at request time. See Astro middleware.
Create a server endpoint for the source MDX:
Add middleware to rewrite Markdown requests to the endpoint and append Vary: Accept to both HTML and Markdown responses:
Astro static output cannot inspect request headers. For a static deployment, publish separate .md files or configure negotiation in the hosting layer.
Plain Vite
Vite produces static files for production. Use this plugin to add content negotiation to its development and preview servers (vite and vite preview):
Use vite preview only for local verification. In production, run equivalent middleware in your application server or configure your CDN or edge platform to route requests based on Accept. For a static-only deployment, publish generated .md files at separate URLs.
Test the result
Verify both representations at the same URL:
The first response should use Content-Type: text/html. The second should use Content-Type: text/markdown. If a CDN or proxy caches the responses, confirm that its cache key includes Accept or that the response includes Vary: Accept.
Also test negotiation at both /docs and a nested docs URL:
Accept header | Expected response |
|---|---|
Missing, */*, or text/* | HTML |
text/markdown or Text/Markdown | Markdown |
text/html;q=1, text/markdown;q=0.1 | HTML |
text/html;q=0.1, text/markdown;q=1 | Markdown |
text/html, text/markdown;q=0 | HTML |
text/markdown;q=0.000, text/html | HTML |
text/markdown; charset=utf-8; q=0, text/html | HTML |
text/markdown;q=0.5 | Markdown |
text/html;q=0, text/markdown | Markdown |
Repeat requests in HTML → Markdown → HTML → Markdown order to check that the deployed cache keeps the representations separate. Check that Markdown responses contain source MDX, including frontmatter, rather than rendered HTML. On a static-only deployment, expect HTML at the docs URL for every header above.