From 0ea3e0d38c88028305e15066b188c8c475b873a1 Mon Sep 17 00:00:00 2001 From: DevCalebR Date: Thu, 18 Jun 2026 04:53:39 -0400 Subject: [PATCH] Fix localhost Clerk auth rendering --- app/(auth)/sign-in/[[...sign-in]]/page.tsx | 21 +++++++-------- app/(auth)/sign-up/[[...sign-up]]/page.tsx | 21 +++++++-------- components/auth/clerk-sign-in-card.tsx | 16 ++++++++++++ components/auth/clerk-sign-up-card.tsx | 16 ++++++++++++ lib/clerk-config.ts | 19 ++++++++++++++ tests/clerk-config.test.ts | 30 ++++++++++++++++++++++ tests/public-auth-routing.test.ts | 26 ++++++++++--------- 7 files changed, 113 insertions(+), 36 deletions(-) create mode 100644 components/auth/clerk-sign-in-card.tsx create mode 100644 components/auth/clerk-sign-up-card.tsx diff --git a/app/(auth)/sign-in/[[...sign-in]]/page.tsx b/app/(auth)/sign-in/[[...sign-in]]/page.tsx index 557e85a..28de8eb 100644 --- a/app/(auth)/sign-in/[[...sign-in]]/page.tsx +++ b/app/(auth)/sign-in/[[...sign-in]]/page.tsx @@ -1,19 +1,21 @@ import { auth } from '@clerk/nextjs/server'; -import { SignIn } from '@clerk/nextjs'; +import dynamic from 'next/dynamic'; import { redirect } from 'next/navigation'; import { getAdminSession } from '@/lib/admin'; import { getBusinessForOwnerClerkId } from '@/lib/business-access'; import { - DEFAULT_CLERK_AFTER_AUTH_URL, - DEFAULT_CLERK_SIGN_IN_URL, - DEFAULT_CLERK_SIGN_UP_URL, - hasRequiredValidClerkEnv, + canUseClerkClientComponents, } from '@/lib/clerk-config'; import { resolveSignedInAppDestination } from '@/lib/public-auth-routing'; +const ClerkSignInCard = dynamic( + () => import('@/components/auth/clerk-sign-in-card').then((module) => module.ClerkSignInCard), + { ssr: false } +); + export default async function SignInPage() { - if (!hasRequiredValidClerkEnv()) { + if (!canUseClerkClientComponents()) { return (
@@ -60,12 +62,7 @@ export default async function SignInPage() {

- +
); diff --git a/app/(auth)/sign-up/[[...sign-up]]/page.tsx b/app/(auth)/sign-up/[[...sign-up]]/page.tsx index c6f77c0..5b910b6 100644 --- a/app/(auth)/sign-up/[[...sign-up]]/page.tsx +++ b/app/(auth)/sign-up/[[...sign-up]]/page.tsx @@ -1,17 +1,19 @@ import { auth } from '@clerk/nextjs/server'; -import { SignUp } from '@clerk/nextjs'; +import dynamic from 'next/dynamic'; import { redirect } from 'next/navigation'; import { getAdminSession } from '@/lib/admin'; import { getBusinessForOwnerClerkId } from '@/lib/business-access'; import { - DEFAULT_CLERK_AFTER_AUTH_URL, - DEFAULT_CLERK_SIGN_IN_URL, - DEFAULT_CLERK_SIGN_UP_URL, - hasRequiredValidClerkEnv, + canUseClerkClientComponents, } from '@/lib/clerk-config'; import { resolveSignedInAppDestination } from '@/lib/public-auth-routing'; +const ClerkSignUpCard = dynamic( + () => import('@/components/auth/clerk-sign-up-card').then((module) => module.ClerkSignUpCard), + { ssr: false } +); + function getIntentCopy(intent: string | undefined) { if (intent === 'pilot') { return { @@ -35,7 +37,7 @@ export default async function SignUpPage({ }: { searchParams?: Record; }) { - if (!hasRequiredValidClerkEnv()) { + if (!canUseClerkClientComponents()) { const intent = typeof searchParams?.intent === 'string' ? searchParams.intent : undefined; const copy = getIntentCopy(intent); @@ -90,12 +92,7 @@ export default async function SignUpPage({

- +
); diff --git a/components/auth/clerk-sign-in-card.tsx b/components/auth/clerk-sign-in-card.tsx new file mode 100644 index 0000000..83d2bb8 --- /dev/null +++ b/components/auth/clerk-sign-in-card.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { SignIn } from '@clerk/nextjs'; + +import { DEFAULT_CLERK_AFTER_AUTH_URL, DEFAULT_CLERK_SIGN_IN_URL, DEFAULT_CLERK_SIGN_UP_URL } from '@/lib/clerk-config'; + +export function ClerkSignInCard() { + return ( + + ); +} diff --git a/components/auth/clerk-sign-up-card.tsx b/components/auth/clerk-sign-up-card.tsx new file mode 100644 index 0000000..9161e68 --- /dev/null +++ b/components/auth/clerk-sign-up-card.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { SignUp } from '@clerk/nextjs'; + +import { DEFAULT_CLERK_AFTER_AUTH_URL, DEFAULT_CLERK_SIGN_IN_URL, DEFAULT_CLERK_SIGN_UP_URL } from '@/lib/clerk-config'; + +export function ClerkSignUpCard() { + return ( + + ); +} diff --git a/lib/clerk-config.ts b/lib/clerk-config.ts index 31905cb..49474b8 100644 --- a/lib/clerk-config.ts +++ b/lib/clerk-config.ts @@ -50,8 +50,27 @@ export function hasRequiredValidClerkEnv(env: EnvMap = process.env) { ); } +export function canUseClerkClientComponents(env: EnvMap = process.env) { + const publishableKey = env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY?.trim() ?? ''; + if (!publishableKey || !isLikelyValidClerkPublishableKey(publishableKey)) { + return false; + } + + // Localhost cannot use the production Clerk frontend API origin, so prefer the + // explicit auth-unavailable fallback instead of mounting broken widgets. + if (env.NODE_ENV !== 'production' && publishableKey.startsWith('pk_live_')) { + return false; + } + + return true; +} + export function resolveClerkPublishableKey(env: EnvMap = process.env) { const configured = env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY?.trim() ?? ''; + if (configured && !canUseClerkClientComponents(env)) { + return CLERK_PREVIEW_FALLBACK_KEY; + } + if (configured && isLikelyValidClerkPublishableKey(configured)) { return configured; } diff --git a/tests/clerk-config.test.ts b/tests/clerk-config.test.ts index 253e697..a80da21 100644 --- a/tests/clerk-config.test.ts +++ b/tests/clerk-config.test.ts @@ -2,10 +2,12 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { + canUseClerkClientComponents, DEFAULT_CLERK_SIGN_IN_URL, DEFAULT_CLERK_SIGN_UP_URL, getClerkAuthUrls, getClerkFrontendApiOrigin, + resolveClerkPublishableKey, } from '../lib/clerk-config.ts'; test('getClerkAuthUrls normalizes env routes to stable base paths for Clerk path routing', () => { @@ -36,3 +38,31 @@ test('getClerkFrontendApiOrigin returns null for invalid publishable keys', () = null ); }); + +test('canUseClerkClientComponents disables broken localhost live-key widgets during local development', () => { + assert.equal( + canUseClerkClientComponents({ + NODE_ENV: 'development', + NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: 'pk_live_Y2xlcmsuY2FsbGJhY2tjbG9zZXIuY29tJA', + }), + false + ); + + assert.equal( + canUseClerkClientComponents({ + NODE_ENV: 'development', + NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: 'pk_test_Y3VyaW91cy1yaGluby00NS5jbGVyay5hY2NvdW50cy5kZXYk', + }), + true + ); +}); + +test('resolveClerkPublishableKey falls back to the preview key for local development with live Clerk keys', () => { + assert.equal( + resolveClerkPublishableKey({ + NODE_ENV: 'development', + NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: 'pk_live_Y2xlcmsuY2FsbGJhY2tjbG9zZXIuY29tJA', + }), + 'pk_test_Y2xlcmsuZXhhbXBsZS5jb20k' + ); +}); diff --git a/tests/public-auth-routing.test.ts b/tests/public-auth-routing.test.ts index 6eac65c..250337a 100644 --- a/tests/public-auth-routing.test.ts +++ b/tests/public-auth-routing.test.ts @@ -43,27 +43,29 @@ test('clerk auth surfaces use explicit path routing and fallback redirects', () assert.match(layout, /signInFallbackRedirectUrl=\{DEFAULT_CLERK_AFTER_AUTH_URL\}/); assert.match(layout, /signUpFallbackRedirectUrl=\{DEFAULT_CLERK_AFTER_AUTH_URL\}/); - assert.match(signInPage, /routing="path"/); - assert.match(signInPage, /fallbackRedirectUrl=\{DEFAULT_CLERK_AFTER_AUTH_URL\}/); - assert.match(signInPage, /