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:
http://localhost:3000localhost:3000✅ Correct:
http://localhost:3000 (for dev), https://yourdomain.com (for prod)http://localhost:3000, https://yourdomain.comhttp:// or https://)---
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
http:// vs https://)Ctrl+C, then npm run dev)---
Official Resources
---
Found a different variation? Drop it in the comments—we update this based on real 2am debugging sessions.