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: Your signInUrl 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

  • Auth pages must be outside ClerkProvider unless using <Outlet /> pattern
  • Middleware matchers should exclude sign-in/sign-up routes
  • signInUrl must match an unprotected route
  • Test with network tab open: if you see repeated 308 redirects to the same URL, you have this issue
  • ---

    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!

    🔥 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