Skip to content
Merged
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,110 @@ export const config = {

// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
// Check for AI/LLM clients and redirect to markdown if appropriate
const markdownRedirect = handleAIClientRedirect(request);
if (markdownRedirect) {
return markdownRedirect;
}

return handleRedirects(request);
}

// don't send Permanent Redirects (301) in dev mode - it gets cached for "localhost" by the browser
const redirectStatusCode = process.env.NODE_ENV === 'development' ? 302 : 301;

/**
* Detects if the user agent belongs to an AI/LLM tool or development environment
* that would benefit from markdown format
*/
function isAIOrDevTool(userAgent: string): boolean {
const patterns = [
/claude/i, // Claude Desktop/Code
/cursor/i, // Cursor IDE
/copilot/i, // GitHub Copilot
/chatgpt/i, // ChatGPT
/openai/i, // OpenAI tools
/anthropic/i, // Anthropic tools
/vscode/i, // VS Code extensions
/intellij/i, // IntelliJ plugins
/sublime/i, // Sublime Text plugins
/got/i, // Got HTTP library (sindresorhus/got)
// Add more patterns as needed
];

return patterns.some(pattern => pattern.test(userAgent));
}

/**
* Handles redirection to markdown versions for AI/LLM clients
*/
const handleAIClientRedirect = (request: NextRequest) => {
const userAgent = request.headers.get('user-agent') || '';
const url = request.nextUrl;

// Determine if this will be served as markdown
const forceMarkdown = url.searchParams.get('format') === 'md';
const isAIClient = isAIOrDevTool(userAgent);
const willServeMarkdown =
(isAIClient || forceMarkdown) && !url.pathname.endsWith('.md');

// Log user agent for debugging (only for non-static assets)
if (
!url.pathname.startsWith('/_next/') &&
!url.pathname.includes('.') &&
!url.pathname.startsWith('/api/')
) {
const contentType = willServeMarkdown ? 'πŸ“„ MARKDOWN' : '🌐 HTML';
console.log(
`[Middleware] ${url.pathname} - ${contentType} - User-Agent: ${userAgent}`
);
}

// Skip if already requesting a markdown file
if (url.pathname.endsWith('.md')) {
return undefined;
}

// Skip API routes and static assets (should already be filtered by matcher)
if (
url.pathname.startsWith('/api/') ||
url.pathname.startsWith('/_next/') ||
/\.(js|json|png|jpg|jpeg|gif|ico|pdf|css|woff|woff2|ttf|map|xml|txt|zip|svg)$/i.test(
url.pathname
)
) {
return undefined;
}

// Check for AI client detection

if (isAIClient || forceMarkdown) {
// Log the redirect for debugging
console.log(
`[Middleware] Redirecting to markdown: ${isAIClient ? 'AI client detected' : 'Manual format=md'}`
);

// Create new URL with .md extension
const newUrl = url.clone();
// Handle root path and ensure proper .md extension
let pathname = url.pathname === '/' ? '/index' : url.pathname;
// Remove all trailing slashes if present, then add .md
pathname = pathname.replace(/\/+$/, '') + '.md';
newUrl.pathname = pathname;

// Clean up the format query parameter if it was used
if (forceMarkdown) {
newUrl.searchParams.delete('format');
}

return NextResponse.redirect(newUrl, {
status: redirectStatusCode,
});
}

return undefined;
};

const handleRedirects = (request: NextRequest) => {
const urlPath = request.nextUrl.pathname;

Expand Down
Loading