Skip to main content

Publish agent-readable content

Generate raw Markdown, llms.txt, and llms-full.txt from the same pages your site already publishes.

Expose a page collection once and let Ginko derive its agent-readable files. You do not need a second content tree or a script that copies Markdown into public/.

Expose a collection

Add site metadata, one section, and an agent policy to the collection:

content.config.ts
import {
  defineAgentSection,
  defineCollection,
  defineContentConfig
} from '@lupinum/ginko-content/config'

export const docs = defineCollection({
  type: 'page',
  source: 'docs/**/*.md',
  route: '/docs',
  agent: {
    section: 'docs',
    markdown: true
  }
})

export default defineContentConfig({
  agent: {
    site: {
      title: 'Acme documentation',
      description: 'Product documentation for Acme.',
      whenToUse: 'Use this site to build and operate Acme products.',
      whenNotToUse: 'Do not use this site for account or billing support.'
    },
    sections: [
      defineAgentSection({
        id: 'docs',
        title: 'Documentation',
        order: 10
      })
    ]
  },
  collections: { docs }
})

agent.markdown: true opts the collection into raw Markdown and both LLM indexes. The section ID must match an entry in agent.sections; use the built-in content section when you do not need custom grouping.

whenToUse gives an agent a direct task-level routing instruction. Keep it specific. Add whenNotToUse when a nearby product or support surface is easy to confuse with this site.

Know which config owns what

content.config.ts decides which pages and metadata appear in agent output. The content.agent module option in nuxt.config.ts controls how Nuxt serves that output.

Agent routes and link headers are enabled by default. Static delivery is the default, so public pages can stay on a CDN. Select runtime delivery when Nitro must negotiate the public page response:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@lupinum/ginko-content'],
  site: {
    url: 'https://docs.example.com'
  },
  content: {
    agent: {
      delivery: 'runtime'
    }
  }
})

delivery: 'static' generates raw Markdown and LLM indexes alongside prerendered HTML. A hosting provider can route Markdown-preferring requests to those files. delivery: 'runtime' lets Nitro crawl pages so their generated data dependencies remain available, then removes the prerendered HTML so Nitro can negotiate each page request. A true static generation still creates complete HTML and agent files.

Nuxt site.url is the one canonical origin for sitemaps, agent indexes, raw Markdown links, and app-owned agent page rendering. Ginko rejects a production build with agent routes when this value is missing. Do not maintain another origin in content.config.ts.

Set content.agent: false to disable every agent route and middleware. This does not change the collection model in content.config.ts.

Use the generated surfaces

For a document served at /docs/getting-started, Ginko exposes:

  • /raw/docs/getting-started.md — normalized Markdown for that page.
  • /llms.txt — a compact, sectioned index with links to raw pages.
  • /llms-full.txt — the selected pages combined into one Markdown document.

Localized sites also get localized raw routes and /<locale>/llms.txt and /<locale>/llms-full.txt. Production agent output excludes drafts. Partials and data collections never become public agent pages.

With runtime delivery, an ordinary page can also return Markdown when a request sends Accept: text/markdown:

terminal
curl -H 'Accept: text/markdown' https://docs.example.com/docs/getting-started

That negotiation runs in Nitro middleware. Static delivery requires a hosting provider recipe to negotiate the same URL. Without one, clients use /raw/**.md, /llms.txt, or /llms-full.txt directly.

On a server deployment, a missing public route requested with Accept: text/markdown keeps its HTTP 404 status and returns a small Markdown recovery document. It links to /llms.txt, /llms-full.txt, and the homepage. API and asset errors are not rewritten. A static host cannot negotiate this response because no Nitro request handler runs there.

Include an app-owned page

Use defineAgentAppPage() for a Nuxt route whose content does not come from a collection, such as a legal or support page. Add the helper to the existing import, then add a pages entry to the root agent object:

agent.pages
pages: [
  defineAgentAppPage({
    id: 'support',
    route: '/support',
    section: 'docs',
    title: 'Support',
    description: 'How to get help with Acme.',
    render: () => '# Support\n\nEmail support@example.com.'
  })
]

The app owns the HTML route; render() supplies only its agent-facing Markdown.

Serialize an MDC component

Ordinary Markdown needs no extra setup. Register a server-side serializer when a custom component needs a clearer text representation:

server/plugins/agent-markdown.ts
import {
  blockquoteMarkdown,
  getMarkdownProp,
  registerAgentMarkdownSerializers,
  type AgentMarkdownSerializer
} from '@lupinum/ginko-content/agent'
import { defineNitroPlugin } from 'nitropack/runtime'

const renderCallout: AgentMarkdownSerializer = (node, context) => {
  const title = getMarkdownProp(node, 'title') || 'Note'
  return blockquoteMarkdown(`**${title}**\n\n${context.renderChildren(node)}`)
}

export default defineNitroPlugin(() => {
  registerAgentMarkdownSerializers({
    callout: renderCallout,
    MdcCallout: renderCallout
  })
})

Register both the authored tag and its component alias when the parser can produce either name. Keep the serializer deterministic and free of private application state: its output is public content.

Verify static output

Generate the site, then inspect the public artifacts:

terminal
pnpm generate
find .output/public/raw -type f -name '*.md'

Open .output/public/llms.txt and .output/public/llms-full.txt as part of deployment review. Check that expected routes are present and that drafts, partials, and private data are absent.