// Content negotiation between the SPA's HTML shell and raw markdown. // // The explorer's primary audience includes AI agents and CLI tools that never // execute JavaScript — to them the built SPA shell is an empty page. Any // client that does not advertise `text/html` in its Accept header receives // markdown instead. // // Social and search crawlers are the exception: many of them send a wildcard // Accept header exactly like curl does, but they consume the meta-tagged HTML // shell (OG cards, canonical URLs). They are recognized by user agent and // always receive HTML, so link previews keep working. const HTML_CRAWLERS = /googlebot|bingbot|duckduckbot|yandex|baiduspider|applebot|facebookexternalhit|facebot|twitterbot|slackbot|slack-imgproxy|discordbot|linkedinbot|whatsapp|telegrambot|pinterestbot|redditbot|embedly|iframely|skypeuripreview/i; /** Responses that negotiate on these headers must declare it to caches. */ export const NEGOTIATION_VARY = 'Accept, User-Agent'; export function prefersHtml(request: Request): boolean { const accept = request.headers.get('accept') ?? ''; if (/\btext\/html\b/i.test(accept)) return true; return HTML_CRAWLERS.test(request.headers.get('user-agent') ?? ''); } export function markdownResponse( body: string, init: { status?: number; cacheControl: string }, ): Response { return new Response(body, { status: init.status ?? 200, headers: { 'Cache-Control': init.cacheControl, 'Content-Type': 'text/markdown; charset=utf-8', 'X-Content-Type-Options': 'nosniff', Vary: NEGOTIATION_VARY, }, }); }