Clerk: redirect loop after authentication [2026 fix]

Redirect URI mismatch between Clerk dashboard and app code causes infinite auth loops. Fix: sync your redirect URIs and remove duplicate middleware.

Clerk: Redirect Loop After Authentication

TL;DR

Cause: Your Clerk redirect URI in the dashboard doesn't match your app's callback route, or you have duplicate <ClerkProvider> wrappers causing middleware to run twice.

Fix: Verify NEXT_PUBLIC_CLERK_SIGN_IN_URL matches your actual callback endpoint, and ensure you're not wrapping your app in multiple <ClerkProvider> components.

---

Real Console Error Messages

When you hit this bug at 2am, watch for these exact messages:

``` Error: Clerk: redirect loop detected. Infinite redirects to /auth/callback ```

``` WARN [ClerkProvider] Detected multiple ClerkProvider instances. This causes duplicate middleware execution and redirect loops. ```

``` ERROR: Invalid redirect_uri. Expected "http://localhost:3000/auth/callback" but got "http://localhost:3000/sign-in" ```

``` TypeError: Cannot read property 'redirectUrl' of undefined at ClerkMiddleware.ts:45 ```

``` REDIRECT LOOP: 301 → /auth/callback → /sign-in → /auth/callback (repeated 25 times) ```

---

Broken Code vs. Fixed Code

Problem 1: Mismatched Redirect URIs

BROKEN: ```javascript // pages/api/auth/callback.ts export default async function handler(req, res) { // Clerk dashboard has: http://localhost:3000/auth/signin // But your code expects: /auth/callback res.redirect('/dashboard'); }

// .env.local NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard // Missing the actual callback route configuration ```

FIXED: ```javascript // pages/api/auth/callback.ts - Keep this endpoint export default async function handler(req, res) { res.redirect('/dashboard'); }

// .env.local - Add these matching variables NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard

// Then in Clerk Dashboard > API Keys > Redirect URIs: // Add: http://localhost:3000/auth/callback (exactly) // Add: http://yourdomain.com/auth/callback (for production) ```

Problem 2: Duplicate ClerkProvider Wrappers

BROKEN: ```typescript // pages/_app.tsx import { ClerkProvider } from '@clerk/nextjs';

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

// pages/index.tsx - Also wrapping here import { ClerkProvider } from '@clerk/clerk-react';

export default function Home() { return ( <ClerkProvider> <div>Content</div> </ClerkProvider> ); } ```

FIXED: ```typescript // pages/_app.tsx - Single provider at root import { ClerkProvider } from '@clerk/nextjs';

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

// pages/index.tsx - Remove the wrapper export default function Home() { return ( <div>Content</div> ); } ```

Problem 3: Middleware Configuration

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

export const config = { matcher: ['/(.*?)'], // Too broad, catches everything };

export default authMiddleware({ publicRoutes: ['/'], // Missing sign-in and sign-up routes }); ```

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

export const config = { matcher: [ '/((?!_next|_clerk|.*\\..*|api/|public).*)', '/api/protected(.*)', ], };

export default authMiddleware({ publicRoutes: ['/', '/sign-in', '/sign-up', '/api/public(.*)'], ignoredRoutes: ['/api/webhook(.*)'], }); ```

---

Version-Specific Notes

Clerk SDK v4.x vs v5.x behavior: We're uncertain whether the redirect URI validation changed between these versions. Check your package.json and cross-reference the [official Clerk changelog](https://clerk.com/changelog). If you're on v4.x and seeing different error messages, please comment below.

---

Still Broken? Check These Too

1. Environment variable typo – Copy-paste your NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY directly from the Clerk dashboard. A single character difference breaks everything. Restart your dev server after updating .env.local.

2. CORS/Origin mismatch – If deployed, verify your production domain is registered in Clerk dashboard under "Allowed Origins." Check [Clerk deployment guide](/?guide=clerk-deployment).

3. Stale browser cache – Hard refresh (Ctrl+Shift+R or Cmd+Shift+R), clear cookies for localhost, and test in an incognito window. Also check [debugging authentication flows](/?guide=auth-debugging).

---

Official Resources

  • [Clerk Next.js Integration Docs](https://clerk.com/docs/quickstarts/nextjs)
  • [Redirect URI Configuration](https://clerk.com/docs/deployments/overview)
  • [ClerkProvider API Reference](https://clerk.com/docs/components/clerk-provider)
  • ---

    Found a different variation? Drop it in the comments – If you hit this error with a unique setup (custom domain, monorepo, serverless, etc.), share your solution to help future 2am debugging sessions.

    🔥 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