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