diff --git a/src/app/api/health/route.js b/src/app/api/health/route.js new file mode 100644 index 00000000..9610fd5c --- /dev/null +++ b/src/app/api/health/route.js @@ -0,0 +1,24 @@ +import { NextResponse } from 'next/server'; +import { getServiceRoleClient } from '@/lib/supabase/service-role.js'; + +export const dynamic = 'force-dynamic'; + +const headers = { 'Cache-Control': 'no-store' }; + +/** + * Health check for status.profullstack.com: one cheap PostgREST HEAD query, + * capped at 3s. Never echoes the error, so nothing about the setup leaks. + */ +export async function GET() { + try { + const { error } = await getServiceRoleClient() + .from('users') + .select('id', { head: true }) + .limit(1) + .abortSignal(AbortSignal.timeout(3000)); + if (error) throw error; + return NextResponse.json({ status: 'ok', db: 'ok' }, { headers }); + } catch { + return NextResponse.json({ status: 'error', db: 'down' }, { status: 503, headers }); + } +} diff --git a/src/middleware.js b/src/middleware.js index 7666693d..7efc23a1 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -23,6 +23,9 @@ export function middleware(request) { return NextResponse.redirect(url, 301); } + // Health checks (status.profullstack.com) are never rate limited. + if (pathname === '/api/health') return NextResponse.next(); + const ip = getClientIp(request); // Rate limiting