Created
March 9, 2026 16:33
-
-
Save seancdavis/85b010dd6b3640e1fd3d460d2480fb60 to your computer and use it in GitHub Desktop.
Plain text markdown for agents
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { Context, Config } from "@netlify/edge-functions"; | |
| /** | |
| * Content negotiation edge function that serves Markdown versions of pages | |
| * when the Accept header prefers text/plain or text/markdown over text/html | |
| */ | |
| export default async (req: Request, context: Context) => { | |
| const url = new URL(req.url); | |
| const pathname = url.pathname; | |
| // Skip if this is already a request for source content | |
| if (pathname.startsWith('/source-content/')) { | |
| return await context.next(); | |
| } | |
| // Skip static assets and API endpoints | |
| const skipPaths = ['/_', '/api/', '/public/', '/images/', '/assets/', '/logos/', '/pdfs/']; | |
| if ( | |
| skipPaths.some(prefix => pathname.startsWith(prefix)) || | |
| pathname.includes('.') && !pathname.endsWith('/') | |
| ) { | |
| return await context.next(); | |
| } | |
| const acceptHeader = req.headers.get('accept') || ''; | |
| // Parse Accept header to check for preference | |
| const acceptTypes = acceptHeader | |
| .split(',') | |
| .map(type => { | |
| const [mediaType, ...params] = type.trim().split(';'); | |
| const qValue = params | |
| .find(p => p.trim().startsWith('q=')) | |
| ?.split('=')[1] || '1.0'; | |
| return { | |
| type: mediaType.trim(), | |
| quality: parseFloat(qValue) | |
| }; | |
| }) | |
| .sort((a, b) => b.quality - a.quality); | |
| // Check if text/plain or text/markdown is preferred over text/html | |
| const plainTextTypes = ['text/plain', 'text/markdown']; | |
| const htmlTypes = ['text/html', 'application/xhtml+xml']; | |
| const bestPlainText = acceptTypes.find(t => plainTextTypes.includes(t.type)); | |
| const bestHtml = acceptTypes.find(t => htmlTypes.includes(t.type)); | |
| const prefersPlainText = bestPlainText && | |
| (!bestHtml || bestPlainText.quality > bestHtml.quality); | |
| if (prefersPlainText) { | |
| // Convert URL path to source content path | |
| // Remove trailing slash and convert to .md path | |
| const cleanPath = pathname.replace(/\/$/, '') || '/index'; | |
| const sourcePath = `/source-content${cleanPath}.md`; | |
| try { | |
| // Create a new request for the markdown version | |
| const sourceUrl = new URL(sourcePath, url.origin); | |
| const sourceRequest = new Request(sourceUrl, { | |
| method: req.method, | |
| headers: req.headers, | |
| body: req.method !== 'GET' && req.method !== 'HEAD' ? req.body : null, | |
| }); | |
| // Try to fetch the markdown version | |
| const sourceResponse = await fetch(sourceRequest); | |
| if (sourceResponse.ok) { | |
| // Create a new response with proper headers | |
| const response = new Response(sourceResponse.body, { | |
| status: sourceResponse.status, | |
| statusText: sourceResponse.statusText, | |
| headers: sourceResponse.headers, | |
| }); | |
| // Ensure correct Content-Type for markdown | |
| response.headers.set('Content-Type', 'text/plain; charset=utf-8'); | |
| response.headers.set('Vary', 'Accept'); | |
| return response; | |
| } | |
| } catch (error) { | |
| // If fetching markdown fails, fall through to serve original | |
| console.log(`Failed to fetch markdown for ${pathname}:`, error); | |
| } | |
| } | |
| // Continue with normal processing, but add Vary header | |
| const response = await context.next(); | |
| // Add Vary header to ensure proper caching | |
| response.headers.set('Vary', 'Accept'); | |
| return response; | |
| }; | |
| export const config: Config = { | |
| path: "/*", | |
| cache: "manual" | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment