From 545d7337948f41f006f9cb0270b4bfff2b08ed2c Mon Sep 17 00:00:00 2001 From: Dries Augustyns Date: Mon, 20 Apr 2026 17:51:02 +0200 Subject: [PATCH] docs: Add content negotiation to apps/wiki --- apps/wiki/app/llms.mdx/[[...slug]]/route.ts | 3 +- apps/wiki/middleware.ts | 59 +++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 apps/wiki/middleware.ts diff --git a/apps/wiki/app/llms.mdx/[[...slug]]/route.ts b/apps/wiki/app/llms.mdx/[[...slug]]/route.ts index 5edfd1a..286a4d6 100644 --- a/apps/wiki/app/llms.mdx/[[...slug]]/route.ts +++ b/apps/wiki/app/llms.mdx/[[...slug]]/route.ts @@ -11,7 +11,8 @@ export async function GET(_req: Request, props: {params: Promise<{slug?: string[ return new Response(await getLLMText(page), { headers: { - 'Content-Type': 'text/markdown', + 'Content-Type': 'text/markdown; charset=utf-8', + 'Vary': 'Accept', }, }); } diff --git a/apps/wiki/middleware.ts b/apps/wiki/middleware.ts new file mode 100644 index 0000000..45c78b2 --- /dev/null +++ b/apps/wiki/middleware.ts @@ -0,0 +1,59 @@ +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; + +type Negotiated = 'markdown' | 'html' | 'none'; + +function parseAccept(accept: string): Array<{ type: string; q: number }> { + return accept.split(',').map(part => { + const segments = part.trim().split(';'); + const type = segments[0]?.trim().toLowerCase() ?? ''; + const qParam = segments.slice(1).find(s => s.trim().startsWith('q=')); + const q = qParam ? parseFloat(qParam.split('=')[1] ?? '1') : 1; + return { type, q: isNaN(q) ? 1 : q }; + }); +} + +function getQ(types: Array<{ type: string; q: number }>, target: string): number { + const exact = types.find(t => t.type === target); + if (exact) return exact.q; + const [main] = target.split('/'); + const sub = types.find(t => t.type === `${main}/*`); + if (sub) return sub.q; + const wildcard = types.find(t => t.type === '*/*'); + return wildcard ? wildcard.q : -1; +} + +function negotiate(accept: string): Negotiated { + if (!accept) return 'html'; + const types = parseAccept(accept); + const mdQ = getQ(types, 'text/markdown'); + const htmlQ = getQ(types, 'text/html'); + if (mdQ <= 0 && htmlQ <= 0) return 'none'; + if (mdQ > 0 && mdQ >= htmlQ) return 'markdown'; + return 'html'; +} + +export function middleware(request: NextRequest) { + const accept = request.headers.get('accept') ?? ''; + const { pathname } = request.nextUrl; + const result = negotiate(accept); + + if (result === 'none') { + return new NextResponse(null, { status: 406, headers: { 'Vary': 'Accept' } }); + } + + if (result === 'markdown') { + const rewriteUrl = new URL(`/llms.mdx${pathname}`, request.url); + const response = NextResponse.rewrite(rewriteUrl); + response.headers.set('Vary', 'Accept'); + return response; + } + + const response = NextResponse.next(); + response.headers.set('Vary', 'Accept'); + return response; +} + +export const config = { + matcher: ['/((?!llms\\.mdx|api/search|_next|.*\\.(?:png|jpg|jpeg|gif|svg|ico|webp|woff|woff2|ttf|css|js)).*)',], +};