diff --git a/frontend/src/auth/clerk.tsx b/frontend/src/auth/clerk.tsx index 691b3a0e..fed6cf24 100644 --- a/frontend/src/auth/clerk.tsx +++ b/frontend/src/auth/clerk.tsx @@ -17,11 +17,26 @@ import { import type { ComponentProps } from "react"; +export function isValidClerkPublishableKey(key: string | undefined): key is string { + if (!key) return false; + // Clerk publishable keys look like: pk_test_... or pk_live_... + // In CI we want builds to stay secretless; if the key isn't present/valid, + // we skip Clerk entirely so `next build` can prerender. + // + // Note: Clerk appears to validate key *contents*, not just shape. We therefore + // use a conservative heuristic to avoid treating obvious placeholders as valid. + const m = /^pk_(test|live)_([A-Za-z0-9]+)$/.exec(key); + if (!m) return false; + const body = m[2]; + if (body.length < 16) return false; + if (/^0+$/.test(body)) return false; + return true; +} + export function isClerkEnabled(): boolean { - // Invariant: Clerk is disabled ONLY when the publishable key is absent. - // If a key is present, we assume Clerk is intended to be enabled and we let - // Clerk fail fast if the key is invalid/misconfigured. - return Boolean(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY); + // IMPORTANT: keep this in sync with AuthProvider; otherwise components like + // may render without a and crash during prerender. + return isValidClerkPublishableKey(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY); } export function SignedIn(props: { children: ReactNode }) { diff --git a/frontend/src/components/providers/AuthProvider.tsx b/frontend/src/components/providers/AuthProvider.tsx index 4da743eb..30000cf1 100644 --- a/frontend/src/components/providers/AuthProvider.tsx +++ b/frontend/src/components/providers/AuthProvider.tsx @@ -3,26 +3,12 @@ import { ClerkProvider } from "@clerk/nextjs"; import type { ReactNode } from "react"; -function isLikelyValidClerkPublishableKey(key: string | undefined): key is string { - if (!key) return false; - // Clerk publishable keys look like: pk_test_... or pk_live_... - // In CI we want builds to stay secretless; if the key isn't present/valid, - // we skip Clerk entirely so `next build` can prerender. - // - // Note: Clerk appears to validate key *contents*, not just shape. We therefore - // use a conservative heuristic to avoid treating obvious placeholders as valid. - const m = /^pk_(test|live)_([A-Za-z0-9]+)$/.exec(key); - if (!m) return false; - const body = m[2]; - if (body.length < 16) return false; - if (/^0+$/.test(body)) return false; - return true; -} +import { isValidClerkPublishableKey } from "@/auth/clerk"; export function AuthProvider({ children }: { children: ReactNode }) { const publishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY; - if (!isLikelyValidClerkPublishableKey(publishableKey)) { + if (!isValidClerkPublishableKey(publishableKey)) { return <>{children}; }