Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/app/api/health/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { NextResponse } from "next/server";
import { createServiceClient } from "@/lib/supabase/service";

export const dynamic = "force-dynamic";

const HEADERS = { "Cache-Control": "no-store" };

/**
* GET /api/health
* Public liveness + database check for status.profullstack.com. One cheap
* HEAD select against profiles, capped at 3s. Never returns error details.
*/
export async function GET() {
try {
const { error } = await createServiceClient()
.from("profiles")
.select("id", { head: true })
.limit(1)
.abortSignal(AbortSignal.timeout(3000));
if (error) throw error;
return NextResponse.json({ status: "ok", db: "ok" }, { headers: HEADERS });
} catch {
return NextResponse.json(
{ status: "error", db: "down" },
{ status: 503, headers: HEADERS },
);
}
}
4 changes: 4 additions & 0 deletions src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ function getClientIp(request: NextRequest): string {
}

export async function proxy(request: NextRequest) {
// The status page's health check skips the gate, the meter and the session
// refresh: it must measure the app, not be billed or throttled by it.
if (request.nextUrl.pathname === "/api/health") return NextResponse.next();

// Crawl gateway first: AI training crawlers get 402 Payment Required (or
// the sales page at /crawl) unless they present a paid pass. People,
// Googlebot and retrieval crawlers fall through to everything below.
Expand Down
Loading