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