nuxt-ai-ready
Covers nuxt-ai-ready v3 on Nuxt 4.6 or Nuxt 5.
The module converts rendered pages to Markdown with mdream and publishes llms.txt, llms-full.txt, and .md twins.
Optional parts add MCP tools, WebMCP tools, and a runtime page index. Config key: aiReady. Docs: https://nuxtseo.com/ai-ready
Setup
Included in @nuxtjs/seo v6. If the app uses @nuxtjs/seo, configure the aiReady key and do not add the module again.
Nuxt SEO uses AI Ready's own defaults. It overrides no aiReady option.
With these defaults the database stays off, so the app does not need drizzle-orm.
The package installs @nuxtjs/robots and @nuxtjs/sitemap and loads them as module dependencies. Configure them with the robots and sitemap keys.
npx nuxt module add nuxt-ai-readySet site.url. Canonical URLs, Link headers, and /.well-known/ai-catalog.json use it.
Prerender the pages you want in llms-full.txt and in the page index (nuxt generate, or nitro.prerender.routes).
A page visit in dev or production does not index anything.
Automatic behaviour
With no aiReady config, the module adds:
/llms.txt: site name, description, resource links, and current sitemap pages. Stored pages supply metadata. Large lists split under/llms/./llms-full.txt: the full Markdown of each prerendered page. Only prerendered pages go in the static file.- A
.mdtwin per page./aboutbecomes/about.md, and/becomes/index.md. Paths under/apiand/_get none. - Accept negotiation: a request that prefers
text/markdowngets a307to the.mdURL. Responses sendVary: Accept. AI bot user agents get Markdown only withbotNegotiation: true. <link rel="alternate" type="text/markdown">and<link rel="describedby" href="/llms.txt">in HTML, plus matchingLinkheaders./sitemap.md, and a## Sitemapfooter on every generated.mdpage. SetsitemapMd: falseto remove both.- Nuxt Content v3
pagecollections: the.mdroute serves the source Markdown, not converted HTML. SetcontentSource: falseto convert HTML. - Agent skills:
public/skills/<name>/SKILL.mdin the project or a local layer is published at/.well-known/agent-skills/,/skills/<name>/SKILL.md, and inllms.txt. Custom public directories are supported. One local skill is also served at/SKILL.md. SetagentSkills: falseto publish nothing. Content-Signal: search=yes, ai-input=yesinrobots.txt. SetcontentSignal: falseto remove it.
Off by default: MCP, WebMCP, runtime sync, cron, and the database.
Common tasks
Add llms.txt sections. notes renders before the first heading:
export default defineNuxtConfig({
aiReady: {
llmsTxt: {
sections: [{ title: 'API Reference', links: [{ title: 'REST API', href: '/docs/api', description: 'API docs' }] }],
notes: 'Built with Nuxt AI Ready',
},
},
})Publish separate discovery files from named sitemaps. Use module configuration before writing sitemap and database middleware:
export default defineNuxtConfig({
aiReady: {
llmsFullTxt: false,
llmsTxt: {
markdownLinks: true,
sitemaps: ['pages', 'skills', 'demos'],
sections: [
{ title: 'Skills', sitemap: 'skills', path: '/llms/skills.txt' },
{ title: 'Demos', sitemap: 'demos', path: '/llms/demos.txt' },
],
},
},
})The root links each section file. Chunked sitemaps keep their configured name.
Every file stays within maxLength, default 80,000 characters. Static builds emit every linked child.
GET and HEAD work on every generated discovery file. Empty sections produce no link or file.
For manual discovery, place public/llms.txt. The module preserves it and keeps Markdown conversion enabled.
If a server handler owns discovery, set llmsTxt: false.
Use llmsFullTxt: false instead of redirecting the full export to the index.
Remove an element from the Markdown on every path, build and runtime. Put it in mdreamOptions; the defaults minimal and clean stay on:
export default defineNuxtConfig({
aiReady: { mdreamOptions: { filter: { exclude: ['.author-bio'] } } },
})Change the Markdown text. The Nitro hook covers runtime .md responses, prerendered .md files, llms-full.txt, and indexed pages. ctx.isPrerender is true only during prerendering:
// server/plugins/markdown.ts
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('ai-ready:page:markdown', (ctx) => {
ctx.markdown += '\n\nSee /support.'
})
})Content Signals. Without config, robots.txt gets Content-Signal: search=yes, ai-input=yes and no ai-train. A robots group or merged robots.txt that already sets Content-Signal replaces the default. contentSignal: false removes the line. An object states the whole policy, so an omitted permission renders as no:
export default defineNuxtConfig({ aiReady: { contentSignal: { search: true, aiInput: true, aiTrain: false } } })
// robots.txt: Content-Signal: ai-train=no, search=yes, ai-input=yesMCP: install @nuxtjs/mcp-toolkit and add it to modules. The module then registers list_pages, search_pages, and get_page_markdown at /mcp. These tools need the database, so also run npx nypm add drizzle-orm@1.0.0-rc.4. MCP needs a running server; nuxt generate output cannot host it. The server card is served at /mcp/server-card, /.well-known/mcp/server-card.json, and /.well-known/mcp.json. llms.txt links the card, never the endpoint, because the endpoint answers GET with 405 or 406. mcpServerCard: false removes the card routes and the llms.txt entry.
WebMCP: set webmcp: true. This registers the same three tools in the browser and auto-imports useWebMcpTool() and useWebMcpSupported(). The tools need the database and drizzle-orm. Without webmcp, those composables are not defined.
Runtime indexing, database drivers, Cloudflare D1, and the server query helpers: see references/runtime-indexing.md.
Traps
- Discovery is not authorization. Selected sitemaps exclude stale rows from
llms.txt; they do not delete stored content or protect other APIs. - Custom route policies must allow Markdown twins. If middleware rejects
/skills/planning.md, allow it whenever/skills/planningis allowed. - The same edit in both hooks applies twice. During prerendering the Nitro
ai-ready:page:markdownhook runs, then the Nuxt hook of the same name. Put an edit in one of them, not both. - Content source pages skip conversion hooks. A Nuxt Content page ignores
mdreamOptionsand both Nitro hooks. - Dev has no page data. Query helpers, MCP tools, and search return empty results in
nuxt dev. Test withnuxt buildandnode .output/server/index.mjs. - Cloudflare static pages negotiate only with
contentNegotiation: true. The default leaves prerendered HTML on the asset service, soAccept: text/markdowngets HTML there. Withtrue, prerendered pages go intoassets.run_worker_first: exact paths up to Cloudflare's 100 entries, directory globs such as/docs/*past that. More than 100 top-level pages fail the build; set broader patterns yourself. - Response caching turns negotiation off. An ISR,
swr, orcacheroute rule withoutvaries: ['accept']serves HTML to Markdown clients. WithbotNegotiation: true, the rule needsvaries: ['accept', 'sec-fetch-dest', 'user-agent']. The.mdURL still works. For a CDN that caches by URL only, setcontentNegotiation: false. - Emptying
ctx.markdowndoes not hide a page. It drops the body fromllms-full.txt. The URL stays inllms.txtand in the index. Usesitemap.excludefor discovery, and access control for private pages. - Root
skills/folders require explicit publication. Automatic discovery scans public directories only. SetagentSkills.dir: 'skills'to publish root skills intentionally. - The same hook name exists twice.
ai-ready:page:markdownis a Nuxt hook (nuxt.confighooks, prerender only) and a Nitro hook (nitroApp.hooks, every HTML conversion). They receive different context. The Nitroctx.eventis undefined when indexing runs outside a request. database: falseconflicts withruntimeSyncandcron. The build fails and names the option.- A database feature without
drizzle-orm1.x fails the build.runtimeSync,cron, MCP site tools, WebMCP site tools, and an explicitdatabaseoption turn on the database. Adrizzle-orm0.x install fails too. The error names the feature and the install command. If MCP Toolkit serves only your own tools, setdatabase: falseinstead.
Version limits
- v3 requires Nuxt
^4.6.0 || ^5.0.0and Node^22.22.3 || ^24.15.0 || >=26.0.0. Upgrade Nuxt before the module. - v3 does not install
drizzle-orm. It is an optional peer. If a feature uses the database, runnpx nypm add drizzle-orm@1.0.0-rc.4. - v3 removed the
better-sqlite3fallback. SQLite usesnode:sqlite. Removebetter-sqlite3if only this module used it. - v3 negotiates on
Acceptonly. To serve Markdown to AI bot user agents again, setbotNegotiation: trueand vary caches onaccept,sec-fetch-dest, anduser-agent. - v3 discovers skills in
public/skills/<name>/SKILL.mdby default. Rootskills/stays private. Move public skills, or setagentSkills.dir: 'skills'. - v3 needs
@nuxtjs/robotsv7,@nuxtjs/sitemapv9, andnuxt-site-configv5. Upgrade direct dependencies on them. - v3 adds typed entries. Import server functions from
#ai-ready/serverand app functions from#ai-ready/app. - v2 removed IndexNow. The
indexNowoption and/__ai-ready/indexnowno longer exist. Submit URLs from theai-ready:page:indexedNitro hook: https://nuxtseo.com/docs/ai-ready/advanced/indexnow - v1 renamed these. Do not write the old form:
| Old | New |
|---|---|
mdreamOptions: { preset: 'minimal' } |
mdreamOptions: { minimal: true } |
cacheMaxAgeSeconds |
llmsTxtCacheSeconds |
Nitro hook ai-ready:markdown |
ai-ready:page:markdown |
BulkDocument type |
PageDocument |
JSON.parse(page.headings) |
page.headings (already an array) |
?secret=TOKEN on /__ai-ready/* |
Authorization: Bearer TOKEN header |
Config
llmsTxt(object): generated discovery. Setfalsefor application-owned server routes. A publicllms.txtdisables generation automatically.llmsTxt.pages('sitemap'): sitemap admission. Use'all'for stored pages outside the sitemap, orfalsefor configured sections only.llmsTxt.sitemaps: selected sitemap names. An empty array stops sitemap discovery.llmsTxt.maxLength(80000): maximum characters per file. Minimum1024. Larger lists split without dropping links.llmsFullTxt(true): generate the full export and advertise its link. Setfalseto omit both.llmsTxt.directive({ markdown: true, html: false }): a> Index of every page on this site: [llms.txt](...)line after the frontmatter of each page's.md;html: trueadds a visually hidden/llms.txtlink at the top of each HTML body.llmsTxt.markdownLinks(true): link page entries inllms.txtto their.mdtwins.falselinks canonical URLs. Withsite.urlset, page links inllms.txt,sitemap.md, and.mdbodies are absolute.describedby(true): thedescribedbylink tag and header.tools.<listPages|searchPages|getPageMarkdown>.<mcp|webmcp>.enabled: attach a built in tool to one transport only.autoI18n(true): with@nuxtjs/i18n, adds hreflangLinkheaders and a languages section inllms.txt.- Other options: https://nuxtseo.com/docs/ai-ready/api/config
Debug
curl -H 'Accept: text/markdown' -D - -o /dev/null <url>must return307withLocation: <path>.md. A200means a cache rule turned negotiation off./__ai-ready__/debug.jsonexists in dev, or withdebug: true.- Nuxt DevTools has an AI Ready tab. The panel needs
nuxtseo-devtools-hostas a dev dependency.