From 29fa209178566e2db53ab5cbae8fea5f80a508c4 Mon Sep 17 00:00:00 2001 From: Jeremias Menichelli Date: Thu, 30 Apr 2026 16:49:05 +0200 Subject: [PATCH] chore: Update markdown middleware --- .claude/settings.json | 11 +++---- apps/docs/app/api/md/[...slug]/route.ts | 40 +++++++++++++++++++++++++ apps/docs/middleware.ts | 24 ++++++++++----- 3 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 apps/docs/app/api/md/[...slug]/route.ts diff --git a/.claude/settings.json b/.claude/settings.json index c3fcf7b694..182a010546 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,4 +1,10 @@ { + "permissions": { + "allow": [ + "Bash(node -e \":*)", + "Bash(grep -rn \"guides-md\\\\|api/md\\\\|copyAsMarkdown\\\\|copy-as-markdown\\\\|CopyAsMarkdown\" /Users/jeremias/supabase/supabase/apps/docs/ --include=*.ts --include=*.tsx -l)" + ] + }, "hooks": { "SessionStart": [ { @@ -23,10 +29,5 @@ ] } ] - }, - "permissions": { - "allow": [ - "Bash(node -e \":*)" - ] } } diff --git a/apps/docs/app/api/md/[...slug]/route.ts b/apps/docs/app/api/md/[...slug]/route.ts new file mode 100644 index 0000000000..a0255401a9 --- /dev/null +++ b/apps/docs/app/api/md/[...slug]/route.ts @@ -0,0 +1,40 @@ +import { promises as fs } from 'fs' +import path from 'path' +import { BASE_PATH } from '~/lib/constants' +import { NextResponse } from 'next/server' + +const BASE_DIR = path.join(process.cwd(), 'public/docs') + +export async function GET(request: Request, { params }: { params: Promise<{ slug: string[] }> }) { + const { slug } = await params + const slugPath = slug.join('/') + + // Resolve candidate paths: exact match first, then index fallback for directory-style routes + const candidates = [ + path.join(BASE_DIR, `${slugPath}.md`), + path.join(BASE_DIR, slugPath, 'index.md'), + ] + + for (const filePath of candidates) { + // Prevent path traversal + if (!filePath.startsWith(BASE_DIR + path.sep)) continue + + try { + const content = await fs.readFile(filePath, 'utf-8') + return new NextResponse(content, { + headers: { + 'Content-Type': 'text/markdown; charset=utf-8', + 'Cache-Control': 'public, max-age=86400, stale-while-revalidate=3600', + }, + }) + } catch { + // Try next candidate + } + } + + // Markdown file not found — redirect to the original page so HTML is served instead. + // nomd=1 tells the middleware to skip markdown interception and avoid an infinite loop. + const pageUrl = new URL(`${BASE_PATH ?? ''}/${slugPath}`, request.url) + pageUrl.searchParams.set('nomd', '1') + return NextResponse.redirect(pageUrl, { status: 302 }) +} diff --git a/apps/docs/middleware.ts b/apps/docs/middleware.ts index 34cf26138b..12e6be2555 100644 --- a/apps/docs/middleware.ts +++ b/apps/docs/middleware.ts @@ -1,8 +1,7 @@ -import { isbot } from 'isbot' -import { NextResponse, type NextRequest } from 'next/server' - import { clientSdkIds } from '~/content/navigation.references' import { BASE_PATH } from '~/lib/constants' +import { isbot } from 'isbot' +import { NextResponse, type NextRequest } from 'next/server' const REFERENCE_PATH = `${BASE_PATH ?? ''}/reference` @@ -11,14 +10,23 @@ const GUIDES_PATH = `${BASE_PATH ?? ''}/guides` export function middleware(request: NextRequest) { const url = new URL(request.url) + // nomd=1 is set by the /api/md route when a markdown file is not found, + // so the middleware skips interception and the normal HTML page is served. const requestsMarkdown = - request.headers.get('Accept')?.includes('text/markdown') || url.pathname.endsWith('.md') + !url.searchParams.has('nomd') && + (request.headers.get('Accept')?.includes('text/markdown') || url.pathname.endsWith('.md')) - // Serve pre-generated .md files before the [[...slug]] page route can intercept them - if (url.pathname.startsWith(GUIDES_PATH + '/') && requestsMarkdown) { - const slug = url.pathname.replace(`${GUIDES_PATH}/`, '').replace(/\.md$/, '') + // Serve pre-generated .md files for guides and reference routes. + // The slug is the full path relative to BASE_PATH (e.g. guides/foo or reference/javascript). + if ( + (url.pathname.startsWith(GUIDES_PATH + '/') || + url.pathname.startsWith(REFERENCE_PATH + '/') || + url.pathname === REFERENCE_PATH) && + requestsMarkdown + ) { + const slug = url.pathname.replace(`${BASE_PATH ?? ''}/`, '').replace(/\.md$/, '') const rewriteUrl = new URL(url) - rewriteUrl.pathname = `${BASE_PATH ?? ''}/api/guides-md/${slug}` + rewriteUrl.pathname = `${BASE_PATH ?? ''}/api/md/${slug}` return NextResponse.rewrite(rewriteUrl) }