fix: Implement content negotiation for markdown and html in middleware

This commit is contained in:
Dries Augustyns
2026-04-20 17:20:34 +02:00
parent df76be1b87
commit 37ed1e49b7
+34 -8
View File
@@ -1,22 +1,48 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
function acceptsMarkdown(accept: string): boolean {
for (const part of accept.split(',')) {
type Negotiated = 'markdown' | 'html' | 'none';
function parseAccept(accept: string): Array<{ type: string; q: number }> {
return accept.split(',').map(part => {
const segments = part.trim().split(';');
if (segments[0]?.trim().toLowerCase() !== 'text/markdown') continue;
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 !isNaN(q) && q > 0;
}
return false;
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 (acceptsMarkdown(accept)) {
if (result === 'none') {
return new NextResponse(null, { status: 406, headers: { 'Vary': 'Accept' } });
}
if (result === 'markdown') {
const headers = new Headers(request.headers);
headers.set('x-md-path', pathname);
const response = NextResponse.rewrite(new URL('/api/md', request.url), { request: { headers } });