Skip to content

Commit a2fecde

Browse files
codydeclaude
andcommitted
feat: add AI client user-agent detection for automatic markdown serving
- Detect AI/LLM tools (Claude, Cursor, Copilot, etc.) via user-agent - Automatically redirect to .md versions for better LLM consumption - Add manual override with ?format=md query parameter - Preserve existing redirect functionality 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent e52aeca commit a2fecde

File tree

1 file changed

+70
-0
lines changed

1 file changed

+70
-0
lines changed

src/middleware.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,82 @@ export const config = {
1919

2020
// This function can be marked `async` if using `await` inside
2121
export function middleware(request: NextRequest) {
22+
// Check for AI/LLM clients and redirect to markdown if appropriate
23+
const markdownRedirect = handleAIClientRedirect(request);
24+
if (markdownRedirect) {
25+
return markdownRedirect;
26+
}
27+
2228
return handleRedirects(request);
2329
}
2430

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

34+
/**
35+
* Detects if the user agent belongs to an AI/LLM tool or development environment
36+
* that would benefit from markdown format
37+
*/
38+
function isAIOrDevTool(userAgent: string): boolean {
39+
const patterns = [
40+
/claude/i, // Claude Desktop/Code
41+
/cursor/i, // Cursor IDE
42+
/copilot/i, // GitHub Copilot
43+
/chatgpt/i, // ChatGPT
44+
/openai/i, // OpenAI tools
45+
/anthropic/i, // Anthropic tools
46+
/vscode/i, // VS Code extensions
47+
/intellij/i, // IntelliJ plugins
48+
/sublime/i, // Sublime Text plugins
49+
// Add more patterns as needed
50+
];
51+
52+
return patterns.some(pattern => pattern.test(userAgent));
53+
}
54+
55+
/**
56+
* Handles redirection to markdown versions for AI/LLM clients
57+
*/
58+
const handleAIClientRedirect = (request: NextRequest) => {
59+
const userAgent = request.headers.get('user-agent') || '';
60+
const url = request.nextUrl;
61+
62+
// Skip if already requesting a markdown file
63+
if (url.pathname.endsWith('.md')) {
64+
return undefined;
65+
}
66+
67+
// Skip API routes and static assets (should already be filtered by matcher)
68+
if (url.pathname.startsWith('/api/') ||
69+
url.pathname.startsWith('/_next/') ||
70+
url.pathname.includes('.')) {
71+
return undefined;
72+
}
73+
74+
// Check for explicit format request via query parameter
75+
const forceMarkdown = url.searchParams.get('format') === 'md';
76+
const isAIClient = isAIOrDevTool(userAgent);
77+
78+
if (isAIClient || forceMarkdown) {
79+
// Create new URL with .md extension
80+
const newUrl = url.clone();
81+
// Handle root path and ensure proper .md extension
82+
const pathname = url.pathname === '/' ? '/index' : url.pathname.replace(/\/$/, '');
83+
newUrl.pathname = pathname + '.md';
84+
85+
// Clean up the format query parameter if it was used
86+
if (forceMarkdown) {
87+
newUrl.searchParams.delete('format');
88+
}
89+
90+
return NextResponse.redirect(newUrl, {
91+
status: redirectStatusCode,
92+
});
93+
}
94+
95+
return undefined;
96+
};
97+
2898
const handleRedirects = (request: NextRequest) => {
2999
const urlPath = request.nextUrl.pathname;
30100

0 commit comments

Comments
 (0)