Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save seancdavis/85b010dd6b3640e1fd3d460d2480fb60 to your computer and use it in GitHub Desktop.

Select an option

Save seancdavis/85b010dd6b3640e1fd3d460d2480fb60 to your computer and use it in GitHub Desktop.
Plain text markdown for agents
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