Index of every page on this site: llms.txt

Request an eligible page with a .md suffix to read it as Markdown:

curl https://example.com/about.md

mdream converts rendered HTML during prerendering and at runtime. Nuxt Content pages can use their content source instead.

Build-Time

During nuxi generate:

  1. The module queues a Markdown route for each eligible rendered page, such as /about/ → /about/index.md.
  2. The middleware reads content source or converts rendered HTML. HTML conversion runs the Nitro conversion hooks.
  3. The Nuxt ai-ready:page:markdown hook can change the result.
  4. The module stores page data in SQLite and appends content to llms-full.txt.
  5. Nuxt writes the Markdown route as a static file.

Only a route that renders as HTML gets a Markdown file. A JSON, XML or text route, such as /.well-known/api-catalog or a feed, gets none.

Metadata extracted: title, description, headings, updatedAt (from article:modified_time etc).

HTML conversion also preserves Open Graph and Twitter image fields under meta in frontmatter. JSON-LD scripts become JSON fences at the end of the page body. This metadata reaches runtime twins, prerendered twins, llms-full.txt, and indexed Markdown through the same conversion. Full exports keep image metadata in a YAML fence beside each page's labels. Image metadata changes also set contentChanged when the runtime index refreshes the page.

The module accepts JSON objects and arrays of objects from application/ld+json scripts. Each page keeps up to 16 blocks and 64 KiB of JSON fences, including separators. Malformed JSON, scalar values, and blocks exceeding the remaining limit are skipped. Later smaller blocks can still fit. Executable scripts and other JSON payloads remain excluded. The ai-ready:page:markdown hook receives the preserved metadata and can change or remove it.

Runtime

Explicit .md requests select Markdown directly. For other eligible page URLs, the module checks Accept only by default. Missing or wildcard Accept headers return HTML. Normal browser requests also prefer HTML. It uses the negotiated media preference rather than requiring an exact header string.

Set aiReady.botNegotiation: true to offer Markdown to recognized AI bots whose Accept header prefers HTML. This optional mode also reads User-Agent and Sec-Fetch-Dest. Bot detection does not override browser navigation or explicit Markdown rejection.

To request Markdown through content negotiation, follow the redirect:

curl -L -H "Accept: text/markdown" https://example.com/about

When the Accept header negotiates markdown on a non-.md URL, the module issues a 307 redirect to the .md twin so HTML and markdown variants live under separate cache keys.

The final Markdown response also includes Vary: Accept, including prerendered page twins. Existing variation headers remain intact. Static hosts receive these headers through Nitro route rules and the generated _headers file.

Negotiation runs ahead of the Nitro static asset handler. A prerendered route served by your own Nitro server, such as the node-server preset, negotiates the same way a server-rendered route does.

On Cloudflare Workers, automatic negotiation preserves your existing asset routing. Static HTML stays on Cloudflare's asset service. Explicit .md URLs remain available. Routes already served by your Worker still negotiate Markdown.

For static pages, enable Cloudflare Markdown for Agents in AI Crawl Control. Cloudflare converts requests that accept text/markdown without routing static HTML through your Worker. The feature requires a Pro, Business, Enterprise, or SSL for SaaS plan. You can restrict it to specific paths with a Configuration Rule. If you set an origin Content-Signal header, Cloudflare preserves your policy. Otherwise, its converted responses allow AI training, search, and AI input by default.

If you need module-managed negotiation for static pages, explicitly set aiReady.contentNegotiation: true. The module then adds prerendered page paths to assets.run_worker_first. Every matching HTML request invokes your Worker, including normal browser requests, while preserving the static response. Existing Worker paths still run application middleware. Explicit routing booleans and exclusion patterns remain unchanged. Cloudflare allows 100 routing patterns, and your own patterns count toward that limit. If exact page paths exceed it, the module replaces the largest groups of pages with directory globs, such as /docs/*. A glob also sends other requests under that directory, such as images, to your Worker. The Worker passes static files on to the asset service. Top-level pages have no directory to glob. If they alone exceed the limit, the build fails, and you configure broader patterns yourself. Pages-only deployments still need negotiation at the hosting layer.

Cache Safety

During development and builds, Nuxt AI Ready warns about configuration that limits agent access:

  • ISR or response caches that disable automatic Markdown negotiation
  • Explicit negotiation settings that bypass cache protection or disable Accept and bot negotiation
  • Static output that needs negotiation at the hosting layer
  • A missing or local site.url instead of the public production URL

Each warning includes a fix and names the route when applicable. Cache warnings provide cache.varies values and a guide link. Warnings appear once per Nuxt instance and account for inherited rules and normalized swr settings. Check external CDN rules and bot challenges on your deployed site.

The module resolves automatic negotiation from the effective route rule on each request. It turns negotiation off for:

  • ISR routes
  • Nitro response caches that do not vary by Accept

If you enable botNegotiation, the cache must also vary by Sec-Fetch-Dest and User-Agent. Leave bot negotiation disabled to avoid creating cache entries for each User-Agent value.

This covers cache and swr route rules. A cache with headersOnly: true does not store the response body, so negotiation remains enabled.

On Nitro 3, Markdown URLs bypass inherited response caching so conversion middleware can run. HTML response caching stays enabled. Keep this exclusion when adding custom Markdown cache rules.

Add negotiation inputs to cache.varies, preserving your existing cache options and variation headers. An HTTP Vary header alone does not change Nitro's cache key.

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/docs/**': {
      cache: {
        maxAge: 3600,
        varies: ['accept'],
      },
    },
  },
})

CDN and reverse proxy rules configured outside Nuxt are not visible at request time. Disable negotiation when one of those caches responses by URL and cannot honor Vary:

nuxt.config.ts
export default defineNuxtConfig({
  aiReady: {
    contentNegotiation: false,
  },
})

/about then stays HTML for every Accept and User-Agent header. /about.md and the Markdown alternate link remain available.

Negotiated redirects include Cache-Control: private, no-store and a complete Vary header. This prevents a compliant shared cache from storing the redirect under the HTML URL. A cache that bypasses the origin can still return cached HTML to an AI client.

Prerendered routes behind a CDN. Negotiation cannot run when the CDN serves HTML without reaching Nitro. Configure the CDN to vary on Accept, or use the discovery options below. If you enable bot negotiation, also include Sec-Fetch-Dest and User-Agent in the CDN cache key.

Two ways agents can still find the markdown URL:

  1. Follow the alternate link. Eligible prerendered HTML pages include <link rel="alternate" type="text/markdown" href="/foo.md"> in the <head>, so HTML-parsing crawlers pick it up.
  2. Request .md directly. The build step writes a Markdown file for each eligible prerendered page and serves it as a static asset.

Nuxt Content Integration

With @nuxt/content v3 installed, page collections can supply Markdown without rendering HTML. The module serializes the stored content tree back to Markdown.

npx nuxt module add @nuxt/content

Define a page collection as you normally would:

content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'

export default defineContentConfig({
  collections: {
    blog: defineCollection({
      type: 'page',
      source: 'blog/**/*.md',
    }),
  },
})

Now /<route>.md returns serialized content with canonical_url in its frontmatter, plus last_updated when the file has a date. For example:

curl https://example.com/blog/hello-world.md
---
title: "Hello World"
description: "A post served from @nuxt/content source markdown."
canonical_url: "https://example.com/blog/hello-world"
last_updated: "2026-04-25T03:43:31.143Z"
---

# Hello from Nuxt Content
...

Routes not backed by a content collection fall through to HTML→mdream conversion. The module auto-detects @nuxt/content at build time, no configuration required.

The content tree preserves Markdown structure, including code fences and MDC components. Serialization can change formatting, so the response is not a byte-for-byte copy of the source file.

Source responses skip HTML conversion, so the module cannot extract metadata added only to the rendered HTML. Include image metadata in the supplied Markdown frontmatter and JSON-LD in a JSON fence. The ai-ready:markdown:source hook preserves both without fetching or rendering HTML.

Every .md response resolves root-relative links and images against the site origin. [the API](/docs/api) becomes [the API](https://example.com/docs/api). An agent that reads the Markdown on its own has no page URL to resolve a root-relative link against. Links inside code fences and inline code keep their text. HTML conversion, Nuxt Content source, the ai-ready:markdown:source hook, the ## Sitemap footer, and /sitemap.md all follow this rule.

Configuration

nuxt.config.ts
export default defineNuxtConfig({
  aiReady: {
    mdreamOptions: {
      minimal: true,
    },
    markdownCacheHeaders: {
      maxAge: 3600,
      swr: true,
    },
  },
})

Hooks

These Nitro conversion hooks run whenever the module converts rendered HTML: runtime .md requests, prerendering, and page indexing. They do not run for content-source responses.

'ai-ready:mdreamConfig'

Filter an element from HTML conversion with the supported filter.exclude option:

server/plugins/mdream-config.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('ai-ready:mdreamConfig', (options) => {
    options.filter = {
      ...options.filter,
      exclude: [...(options.filter?.exclude || []), '.author-bio'],
    }
  })
})

'ai-ready:page:markdown'

Append a source route to the converted Markdown:

server/plugins/markdown-footer.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('ai-ready:page:markdown', (ctx) => {
    ctx.markdown += `\n\nSource: ${ctx.route}`
  })
})

See Nitro Hooks for context types.

Was this page helpful?