Clerk: redirect loop after authentication [2026 fix]

Redirect URI mismatch between Clerk dashboard and app code causes infinite loop. Fix: sync your NEXT_PUBLIC_CLERK_REDIRECT_URL with dashboard settings.

Clerk: Redirect Loop After Authentication [2am Fix]

TL;DR

Cause: Your redirect URI in Clerk dashboard doesn't match your app's environment variable or middleware configuration. Fix: Set NEXT_PUBLIC_CLERK_REDIRECT_URL to match your Clerk dashboard allowed redirect URIs exactly (including protocol and trailing slash).

---

Real Console Error Messages

``` 1. Error: The redirect_uri parameter does not match any of the allowed redirect URIs configured for this application.

2. GET /api/auth/callback/clerk 307 redirect loop (repeated)

3. [clerk] Redirect URI mismatch: received "http://localhost:3000/" but expected "http://localhost:3000"

4. TypeError: Cannot read property 'afterSignInUrl' of undefined at ClerkProvider

5. Warning: useAuth hook called outside of <ClerkProvider> - redirecting to sign-in causes infinite loop ```

---

Broken Code vs. Exact Fix

Problem 1: Environment Variable Mismatch

BROKEN: ```javascript // .env.local NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx CLERK_SECRET_KEY=sk_test_xxx // Redirect URL missing or incorrect

// pages/_app.tsx import { ClerkProvider } from '@clerk/nextjs';

export default function App({ Component, pageProps }) { return ( <ClerkProvider> <Component {...pageProps} /> </ClerkProvider> ); } ```

FIXED: ```javascript // .env.local NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx CLERK_SECRET_KEY=sk_test_xxx NEXT_PUBLIC_CLERK_REDIRECT_URL=http://localhost:3000 // Must match dashboard exactly (no trailing slash for localhost)

// pages/_app.tsx import { ClerkProvider } from '@clerk/nextjs';

export default function App({ Component, pageProps }) { return ( <ClerkProvider afterSignInUrl="/dashboard" afterSignUpUrl="/dashboard" > <Component {...pageProps} /> </ClerkProvider> ); } ```

Problem 2: Middleware Not Configured

BROKEN: ```typescript // middleware.ts (missing or incomplete) // This allows unauthenticated access which breaks redirect flow export function middleware(req) { return NextResponse.next(); } ```

FIXED: ```typescript // middleware.ts import { authMiddleware } from '@clerk/nextjs';

export default authMiddleware({ publicRoutes: ['/', '/sign-in', '/sign-up'], ignoredRoutes: ['/api/webhooks'], });

export const config = { matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'] }; ```

Problem 3: Dashboard Configuration Missing

BROKEN (Clerk Dashboard):

  • Allowed redirect URIs: empty or http://localhost:3000/
  • Your app redirects to: http://localhost:3000 (no trailing slash)
  • Result: infinite redirect loop
  • FIXED (Clerk Dashboard): 1. Go to [Clerk Dashboard](https://dashboard.clerk.com) → Applications → Select your app 2. Navigate to "API Keys" tab 3. Find "Allowed redirect URIs" 4. Add exactly what your .env.local has: - Development: http://localhost:3000 - Production: https://yourdomain.com 5. Save and redeploy

    ---

    Why This Happens

    Clerk's authentication flow validates that your app redirects back to an approved URI for security. When your environment variable, middleware configuration, and Clerk dashboard settings don't align, the OAuth callback fails and tries to redirect again, creating an infinite loop.

    Version note: This guide applies to @clerk/nextjs v4.31.0+. Earlier versions had different redirect handling—if you're on v4.x before .31, consider upgrading or check the legacy configuration below.

    ---

    Still Broken? Check These Too

    1. Cookie domain mismatch If using a custom domain, ensure __session cookie domain matches. Check DevTools → Application → Cookies. The domain should be .yourdomain.com (with dot prefix for subdomains).

    2. Race condition in useRouter() Your useRouter().push() may fire before Clerk hydrates. Wrap redirects in useEffect with dependency on isLoaded from useAuth(): ```typescript const { isLoaded } = useAuth(); useEffect(() => { if (isLoaded && !isSignedIn) router.push('/sign-in'); }, [isLoaded, isSignedIn]); ```

    3. Multiple ClerkProvider instances If you have <ClerkProvider> in both _app.tsx and _document.tsx, remove one. Only wrap at the app root.

    ---

    Verification Checklist

  • [ ] .env.local has NEXT_PUBLIC_CLERK_REDIRECT_URL set
  • [ ] Value matches Clerk Dashboard "Allowed redirect URIs" exactly
  • [ ] No trailing slash for localhost development URLs
  • [ ] middleware.ts imports authMiddleware from @clerk/nextjs
  • [ ] After sign-in/sign-up URLs specified in <ClerkProvider>
  • [ ] Server restarted after env changes (npm run dev)
  • [ ] Clerk session cookies visible in DevTools (Application tab)
  • ---

    Related Resources

    [Debugging auth loops in Next.js](/?guide=nextjs-auth-debugging) | [Environment variable setup walkthrough](/?guide=env-config)

    Official Clerk Docs: [https://clerk.com/docs/nextjs/authenticate-with-nextjs](https://clerk.com/docs/nextjs/authenticate-with-nextjs)

    ---

    Found a different variation? Drop it in the comments—this catches edge cases like Vercel preview deploys, regional CDNs, or custom OAuth flows.

    🔥 0d
    LIVE
    PlanetScale rage spiking Vercel pricing complaints Railway gaining fast Supabase happiness rising Resend loved by devs PlanetScale rage spiking Vercel pricing complaints Railway gaining fast Supabase happiness rising
    DEVELOPER PAIN RADAR // Loading...

    Developers complain.
    Opportunities appear.

    We track what developers are struggling with today — and what opportunities that creates.

    guides today
    avg happiness
    🔥 Pain
    📖 Guides
    🔭 Explore
    👤 Mine
    🔥 Pain Radar — rage scores today
    ↗ share
    💡 Opportunity Feed — pain = market gap
    📈 Tool Momentum
    all scores →
    📖 Latest Guide
    all guides →
    📖 All Guides
    📊 Tool Scores
    + Submit
    📰 Hacker News
    ➕ Submit a Tool
    ← back