Skip to content
Dashboard

Making agent-friendly pages with content negotiation

Link to headingWhat is content negotiation

Link to headingHow agents request markdown

Link to headingImplementing content negotiation in Next.js

next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
async rewrites() {
function markdownRewrite(prefix) {
return {
source: `${prefix}/:path*`,
has: [
{
type: 'header',
key: 'accept',
value: '(.*)text/markdown(.*)',
},
],
destination: `${prefix}/md/:path*`,
};
}
return {
beforeFiles: [markdownRewrite('/blog'), markdownRewrite('/changelog')],
};
},
};
export default nextConfig;

Rewrite requests with Accept: text/markdown to a dedicated markdown endpoint

app/blog/md/[[...slug]]/route.ts
import { notFound } from 'next/navigation';
import { getMarkdownContent } from '@/lib/content';
export async function GET(
_req: Request,
{ params }: { params: Promise<{ slug?: string[] }> }
) {
const { slug } = await params;
const content = getMarkdownContent(slug?.join('/') ?? 'index');
if (!content) {
notFound();
}
return new Response(content, {
headers: {
'Content-Type': 'text/markdown',
},
});
}

Convert CMS rich text to markdown and return it with the correct Content-Type

Link to headingPerformance benefits

Link to headingMarkdown sitemaps for agent discovery

app/blog/sitemap.md/route.ts
import { getAllBlogPosts } from '@/app/content';
export const dynamic = 'force-static';
export async function GET() {
const posts = await getAllBlogPosts();
const lines = posts
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
.map((post) => `- [${post.title}](/blog/${post.slug}.md)`);
const sitemap = `# Blog sitemap\n\n${lines.join('\n')}`;
return new Response(sitemap, {
headers: { 'Content-Type': 'text/markdown' },
});
}

Generate a markdown sitemap listing all blog posts by date

app/docs/sitemap.md/route.ts
import { createTableOfContents, TocItem } from "@lib/content";
function renderTocItems(items: TocItem[], indent = '') {
let sitemap = '';
for (const item of items) {
sitemap += `${indent}- [${item.title}](/${item.path})\n`;
if (item.children) {
sitemap += renderTocItems(item.children, `${indent} `);
}
}
return sitemap;
}
export async function GET() {
const tableOfContents = createTableOfContents(`content`);
const sitemap = `# Documentation sitemap\n\n${renderTocItems(tableOfContents)}`
return new Response(sitemap, { headers: { 'Content-Type': 'text/markdown' } });
}

Generate a hierarchical markdown sitemap for nested documentation

Link to headingMaking your site agent-friendly