stack/docs/src/middleware.ts
BilalG1 609579abab
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482)
2026-05-29 15:21:59 -07:00

77 lines
2.6 KiB
TypeScript

import { trackVisit } from '2027-track';
import { runAsynchronously } from '@hexclave/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*',
],
};