Clerk: redirect loop after authentication [2026 fix]
Redirect loop after Clerk auth happens when signInUrl/signUpUrl point to pages with ClerkProvider wrapping. Fix: move auth pages outside protected routes.
Clerk: Redirect Loop After Authentication [2am Fix]
TL;DR
Cause: YoursignInUrl or signUpUrl configuration points to a page that's wrapped by <ClerkProvider> or protected by <ProtectedRoute>, creating infinite redirect chains.
Fix: Move authentication pages outside the provider or remove redirect logic from pages the unauthenticated user accesses.---
Real Console Error Messages
``` 1. GET /sign-in?redirect_url=%2Fdashboard 308 GET /sign-in?redirect_url=%2Fdashboard 308 GET /sign-in?redirect_url=%2Fdashboard 308 (repeats infinitely)
2. Error: Redirect loop detected. Redirecting from "/sign-in" to "/sign-in?redirect_url=%2Fdashboard"
3. [Clerk] Redirect loop: user redirected from /sign-in back to /sign-in
4. Warning: Maximum call stack size exceeded at ClerkProvider.tsx:142
5. Navigation blocked: /sign-in redirects to /sign-in with redirect_url parameter ```
---
Broken Code vs. Fix
Scenario 1: ClerkProvider wrapping sign-in page
❌ BROKEN: ```jsx // app.jsx or root layout export default function App() { return ( <ClerkProvider publishableKey={CLERK_KEY}> <BrowserRouter> <Routes> <Route path="/sign-in" element={<SignInPage />} /> <Route path="/dashboard" element={<Dashboard />} /> </Routes> </BrowserRouter> </ClerkProvider> ); } ```
When unauthenticated, Clerk redirects to /sign-in, but it's inside ClerkProvider which checks auth status again, triggering another redirect.
✅ FIXED: ```jsx // app.jsx export default function App() { return ( <BrowserRouter> <Routes> {/* Sign-in OUTSIDE ClerkProvider */} <Route path="/sign-in" element={<SignInPage />} /> {/* Protected routes INSIDE ClerkProvider */} <Route element={<ClerkProviderWrapper />}> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/profile" element={<Profile />} /> </Route> </Routes> </BrowserRouter> ); }
function ClerkProviderWrapper() { return ( <ClerkProvider publishableKey={CLERK_KEY}> <Outlet /> </ClerkProvider> ); } ```
---
Scenario 2: Redirect middleware catching sign-in page
❌ BROKEN: ```jsx // middleware.js or route guard export function middleware(request) { const { userId } = auth(); // Redirects ALL unauthenticated users if (!userId) { return NextResponse.redirect(new URL('/sign-in', request.url)); } return NextResponse.next(); }
export const config = { matcher: ['/((?!_next|static).*)'], // Catches /sign-in too! }; ```
The middleware redirects /sign-in requests to /sign-in, creating a loop.
✅ FIXED: ```jsx // middleware.js export function middleware(request) { const { userId } = auth(); const pathname = request.nextUrl.pathname; // Whitelist auth pages const publicPaths = ['/sign-in', '/sign-up', '/forgot-password']; if (publicPaths.includes(pathname)) { return NextResponse.next(); } if (!userId) { return NextResponse.redirect(new URL('/sign-in', request.url)); } return NextResponse.next(); }
export const config = { matcher: ['/((?!_next|static|sign-in|sign-up).*)'], }; ```
---
Scenario 3: Misconfigured signInUrl in ClerkProvider
❌ BROKEN: ```jsx <ClerkProvider publishableKey={key} signInUrl="/app/sign-in" // This page is inside a protected route below > <Routes> <Route element={<ProtectedLayout />}> <Route path="/app/sign-in" element={<SignIn />} /> </Route> </Routes> </ClerkProvider> ```
✅ FIXED: ```jsx <ClerkProvider publishableKey={key} signInUrl="/sign-in" // Matches unprotected route > <Routes> <Route path="/sign-in" element={<SignIn />} /> <Route element={<ProtectedLayout />}> <Route path="/app/*" element={<AppRoutes />} /> </Route> </Routes> </ClerkProvider> ```
---
Still Broken? Check These Too
1. afterSignInUrl mismatch: If afterSignInUrl points to a route that also redirects unauthenticated users, it creates a loop. Set it to a guaranteed-protected route like /dashboard (inside ClerkProvider).
2. Double-wrapped providers: Search your codebase for multiple <ClerkProvider> instances. Only the root should have one. Use <Outlet /> for nested routes. See [nested route protection guide](/?guide=clerk-nested-routes).
3. Session token validation: If using middleware with auth() from @clerk/nextjs, ensure your CLERK_SECRET_KEY is set in .env.local. Expired/missing secrets cause auth failures → redirects. Check [Clerk secrets setup](/?guide=clerk-environment-vars).
---
Key Takeaways
ClerkProvider unless using <Outlet /> pattern---
References
📚 [Clerk Official: Configure URLs](https://clerk.com/docs/references/nextjs/clerk-provider) 📚 [Clerk Official: Middleware Protection](https://clerk.com/docs/nextjs/middleware)
Found a different variation? Drop it in the comments below!