Clerk: redirect loop after authentication [2026 fix]

Redirect URI mismatch between Clerk dashboard and app config causes infinite loop. Fix: ensure all redirect URIs match exactly including protocol and trailing slashes.

Clerk: Redirect Loop After Authentication – 2am Emergency Fix

TL;DR

Cause: Your app's redirect URI doesn't match what's configured in the Clerk dashboard (protocol, domain, or trailing slash mismatch). Fix: Add the exact redirect URI to Clerk dashboard settings under "Redirect URLs" and ensure your middleware uses matching origin URLs.

---

Real Console Error Messages

You'll see one or more of these in your browser console or server logs:

``` 1. [Clerk] Redirect loop detected. Infinite redirect cycle intercepted.

2. Error: OAuth callback validation failed - redirect_uri mismatch

3. Uncaught Error: Invalid redirect URI: http://localhost:3000 does not match allowed URIs

4. [nextjs] Clerk middleware: redirectToSignIn() called in infinite loop

5. GET /api/auth/callback/clerk 308 -> GET /sign-in 308 -> GET /api/auth/callback/clerk (loop) ```

If you see loop indicators in Network tab (same requests repeating 5+ times), you have this issue.

---

Broken Code → Exact Fix

Scenario 1: Next.js Middleware (Most Common)

❌ BROKEN: ```javascript // middleware.ts import { authMiddleware } from "@clerk/nextjs";

export const middleware = authMiddleware({ publicRoutes: ["/", "/pricing"], // Missing: no CLERK_REDIRECT_URL set });

export const config = { matcher: ["/((?!_next|.well-known|static).*)"], }; ```

✅ FIXED: ```javascript // middleware.ts import { authMiddleware } from "@clerk/nextjs";

export const middleware = authMiddleware({ publicRoutes: ["/", "/pricing"], });

export const config = { matcher: ["/((?!_next|\.well-known|static).*)"], };

// .env.local CLERK_REDIRECT_URL=https://yourdomain.com CLERK_FRONTEND_API=https://yourdomain.com ```

What changed: Added explicit CLERK_REDIRECT_URL in .env.local matching your actual domain, and fixed the regex matcher to properly escape the dot in .well-known.

---

Scenario 2: React SPA (Vite/CRA)

❌ BROKEN: ```javascript // main.jsx const clerkPubKey = process.env.REACT_APP_CLERK_PUBLISHABLE_KEY;

ReactDOM.createRoot(document.getElementById('root')).render( <ClerkProvider publishableKey={clerkPubKey}> {/* Missing redirectUrl prop */} <App /> </ClerkProvider> ); ```

✅ FIXED: ```javascript // main.jsx const clerkPubKey = process.env.REACT_APP_CLERK_PUBLISHABLE_KEY;

ReactDOM.createRoot(document.getElementById('root')).render( <ClerkProvider publishableKey={clerkPubKey} navigate={(to) => window.location.href = to} > <App /> </ClerkProvider> );

// .env REACT_APP_CLERK_PUBLISHABLE_KEY=pk_live_xxx REACT_APP_CLERK_REDIRECT_URL=https://yourdomain.com ```

What changed: Added navigate prop and explicit redirect URL environment variable.

---

Scenario 3: Clerk Dashboard Configuration (Critical)

At https://dashboard.clerk.com → Applications → [Your App] → Settings → URLs:

❌ Wrong:

  • Redirect URLs: http://localhost:3000
  • Allowed origins: localhost:3000
  • ✅ Correct:

  • Redirect URLs: http://localhost:3000 (for dev), https://yourdomain.com (for prod)
  • Allowed origins: http://localhost:3000, https://yourdomain.com
  • Include protocol (http:// or https://)
  • Match EXACTLY—no trailing slashes unless your app adds them
  • ---

    Version Clarity

    This guide applies to: Clerk SDK @clerk/nextjs@^5.0 and @clerk/react@^5.0 (2025-2026 versions). If using older versions (v4.x or earlier), the environment variable names differ—check the official docs link below. The core concept (URI mismatch causing loops) is version-agnostic.

    ---

    Still Broken? Check These Too

    1. CORS headers mismatch – Ensure your backend's CORS whitelist includes the exact origin. [Debugging CORS with Clerk →](/?guide=clerk-cors-setup) 2. Multiple sign-in components – If you have both <SignIn /> and <UserButton /> on the same page, they can conflict. Ensure only one authentication entry point per page. 3. Session token expired during redirect – Clear browser cache/cookies and localStorage. Run localStorage.clear() in console, then try again.

    ---

    Verification Checklist

  • [ ] Copied exact URL from browser address bar into Clerk dashboard Redirect URLs
  • [ ] Protocol matches (http:// vs https://)
  • [ ] No trailing slash unless intentional
  • [ ] Environment variables reloaded (restart dev server: Ctrl+C, then npm run dev)
  • [ ] Clerk API key is for the correct environment (dev/prod)
  • [ ] No hardcoded localhost URLs in production build
  • ---

    Official Resources

  • [Clerk Next.js Configuration](https://clerk.com/docs/references/nextjs/overview)
  • [Clerk React Setup Guide](https://clerk.com/docs/references/react/overview)
  • [Environment Variables Reference](https://clerk.com/docs/deployments/clerk-environment-variables)
  • ---

    Found a different variation? Drop it in the comments—we update this based on real 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