vlt / affordances
  • Quickstart
  • Concepts
  • Guides
  • Reference
  • Overview
  • Setup
  • Markers
  • What gets converted
  • Caching and privacy
  • llms.txt and llms-full
  • htmlToMarkdown
  • Routing
  • Advertise it
  1. Plugins
  2. /
  3. Markdown
  4. /
  5. Setup

Setup

Add the md plugin to your Next.js config, then the route handler that renders and converts pages.

With the Affordances graph, add md() to withAffordances's plugins:

next.config.ts
import { withAffordances } from '@affordances/next/config'
import { md } from '@affordances/md/next'

export default withAffordances(nextConfig, { name: 'Store', plugins: [md()] })

On its own, wrap the config with withMd:

next.config.ts
import { withMd } from '@affordances/md/next'

export default withMd(nextConfig)

Then add the route handler. init writes it:

Terminal
vlx @affordances/md initnpx @affordances/md initpnpm dlx @affordances/md inityarn dlx @affordances/md initbunx @affordances/md initdeno run -A npm:@affordances/md init

It's one line:

app/api/affordances/md/[[...path]]/route.ts
export { GET } from '@affordances/md/next'

Routing

The config only adds rewrites, so nothing runs per request until an agent asks:

  • A page requested with Accept: text/markdown is rewritten to the handler. API routes, /_next, and URLs with a file extension are left alone.
  • /blog/post.md and /index.md (for /) are rewritten after Next's own files, so a real .md file in public/ still wins.
  • The handler's own request for the page carries an x-affordances-md-render header, which the rewrites skip, so it can't loop.

Next matches Accept as a pattern, so a header that lists text/markdown with a low q-value still gets Markdown. Browsers never send it, so in practice only agents do.

Advertise it

mdAlternate(path) returns metadata alternates for a page's Markdown twin:

app/blog/[slug]/page.tsx
import { mdAlternate } from '@affordances/md/next'

export const generateMetadata = async ({ params }) => {
  const { slug } = await params
  return { alternates: { canonical: `/blog/${slug}`, ...mdAlternate(`/blog/${slug}`) } }
}

That renders <link rel="alternate" type="text/markdown" href="/blog/post.md">. For /, it points at /index.md.


PreviousMarkdown for every pageNextMarkers

On this page

  • Routing
  • Advertise it
Edit this page

Make your app agent-operable

Give every page a graph and every server action an agent path: HTTP, WebMCP, and MCP, with the same session and the same checks. One command sets it up.

Read the quickstart