stack/docs/src/middleware.ts
Madison 1de2a3d630
2027 tracking (#1223)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->

adds 2027-track npm package and updates middleware. 




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added asynchronous visit tracking to improve analytics collection.

* **Chores**
  * Added a new tracking dependency.
* Refined middleware: switched to a default export with improved typing
and async handling.
* Expanded redirect path mappings and improved header handling for more
reliable navigation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-03-17 09:48:14 -05:00

78 lines
2.6 KiB
TypeScript

import { trackVisit } from '2027-track';
import { runAsynchronously } from '@stackframe/stack-shared/dist/utils/promises';
import { NextFetchEvent, NextRequest, NextResponse } from 'next/server';
export default function middleware(request: NextRequest, event: NextFetchEvent) {
const { pathname } = request.nextUrl;
// Track AI agent visits
const trackPromise = trackVisit({
host: request.headers.get('host') ?? request.nextUrl.host,
path: pathname,
userAgent: request.headers.get('user-agent') ?? '',
accept: request.headers.get('accept') ?? '',
country: request.headers.get('x-vercel-ip-country') ?? undefined,
});
runAsynchronously(trackPromise);
event.waitUntil(trackPromise);
// Redirect old concepts paths to new apps paths
const movedToApps = [
'api-keys',
'emails',
'oauth',
'orgs-and-teams',
'permissions',
'webhooks',
];
if (pathname.startsWith('/docs/concepts/')) {
const pageName = pathname.replace('/docs/concepts/', '');
if (movedToApps.includes(pageName)) {
const url = request.nextUrl.clone();
url.pathname = `/docs/apps/${pageName}`;
return NextResponse.redirect(url, 301); // 301 = permanent redirect
}
}
// Only apply to docs and api pages (not already .mdx requests)
// Match /docs, /docs/, /docs/... and /api, /api/, /api/...
const isDocsPath = pathname === '/docs' || pathname.startsWith('/docs/');
const isApiPath = pathname === '/api' || pathname.startsWith('/api/');
if ((isDocsPath || isApiPath) && !pathname.endsWith('.mdx')) {
const acceptHeader = request.headers.get('accept') ?? '';
// Parse Accept header by splitting on commas to properly handle MIME type ordering
const acceptTypes = acceptHeader.split(',').map((t: string) => t.trim().split(';')[0]);
// Find the index of each MIME type in the Accept header
const plainIndex = acceptTypes.findIndex(
(t: string) => t === 'text/plain' || t === 'text/markdown'
);
const htmlIndex = acceptTypes.findIndex((t: string) => t === 'text/html');
// Prefer markdown if text/plain or text/markdown appears before text/html (or text/html doesn't exist)
const prefersMarkdown = plainIndex !== -1 && (htmlIndex === -1 || plainIndex < htmlIndex);
if (prefersMarkdown) {
// Rewrite to the LLM markdown endpoint
const url = request.nextUrl.clone();
url.pathname = `/llms.mdx${pathname.replace(/^\/(docs|api)/, '')}`;
// Preserve query parameters (platform, framework, etc.)
return NextResponse.rewrite(url);
}
}
return NextResponse.next();
}
export const config = {
matcher: [
'/docs/:path*',
'/api/:path*',
],
};