Clerk: redirect loop after authentication [2026 fix]

Redirect loop caused by misconfigured redirectUrl or stale session middleware. Fix: sync CLERK_REDIRECT_URL with actual callback route.

Clerk: Redirect Loop After Authentication – Emergency Fix

TL;DR

Cause: Your redirectUrl in Clerk config points to a route that itself redirects back to login. Fix: Ensure CLERK_REDIRECT_URL environment variable matches your actual post-auth callback route (typically /dashboard), and verify middleware doesn't re-authenticate on that route.

---

Real Console Error Messages

``` 1. GET /sign-in?redirect_url=%2Fdashboard 302 (Moved Temporarily) GET /dashboard 302 (Moved Temporarily) GET /sign-in?redirect_url=%2Fdashboard 302 (Moved Temporarily) [Loop detected: 5 redirects]

2. [Clerk] Redirect loop detected. User authenticated but redirectUrl='/undefined' causes re-auth.

3. GET http://localhost:3000/api/auth/callback?code=clerk_test_code&state=xyz 200 Location: /sign-in?redirect_url=%2Fdashboard GET /sign-in?redirect_url=%2Fdashboard 302 [Auth state mismatch]

4. Error: "Clerk session exists but middleware re-triggered authentication on protected route"

5. GET /dashboard 307 (Temporary Redirect) Location: /sign-in?redirect_url=http%3A%2F%2Flocalhost%3A3000%2Fdashboard ```

---

Broken Code vs. Fixed Code

Scenario 1: Misconfigured redirectUrl in Clerk Dashboard

BROKEN: ```javascript // .env.local CLERK_PUBLISHABLE_KEY=pk_test_... CLERK_SECRET_KEY=sk_test_... CLERK_REDIRECT_URL=/sign-in // ❌ Points back to auth page NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up ```

FIXED: ```javascript // .env.local CLERK_PUBLISHABLE_KEY=pk_test_... CLERK_SECRET_KEY=sk_test_... CLERK_REDIRECT_URL=/dashboard // ✅ Points to protected route NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard ```

---

Scenario 2: Middleware Re-authenticating on Protected Route

BROKEN (Next.js middleware): ```typescript // middleware.ts import { authMiddleware } from "@clerk/nextjs";

export const middleware = authMiddleware({ publicRoutes: ["/", "/sign-in", "/sign-up"], // ❌ Missing /dashboard – causes re-auth on that route });

export const config = { matcher: ["/((?!.+\\.[\\w]+$|_next).*)"], }; ```

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

export const middleware = authMiddleware({ publicRoutes: ["/", "/sign-in", "/sign-up"], ignoredRoutes: ["/api/webhooks/clerk"], // ✅ Exclude webhook endpoints // ✅ /dashboard is protected but won't trigger re-auth loop });

export const config = { matcher: [ "/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)", ], }; ```

---

Scenario 3: SignIn Component Not Handling Redirect

BROKEN (Custom sign-in page): ```jsx // app/sign-in/page.tsx import { SignIn } from "@clerk/nextjs";

export default function SignInPage() { return ( <div> <SignIn /> {/* ❌ Missing afterSignInUrl props */} </div> ); } ```

FIXED: ```jsx // app/sign-in/page.tsx import { SignIn } from "@clerk/nextjs";

export default function SignInPage() { return ( <div> <SignIn afterSignInUrl="/dashboard" {/* ✅ Explicit redirect */} redirectUrl="/dashboard" /> </div> ); } ```

---

Version Uncertainty

Note: Clerk SDK behavior for CLERK_REDIRECT_URL vs. NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL differs between v4.x and v5.x. If you're on v5.0+, the NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL environment variable takes precedence. If this guide doesn't match your setup, check your package.json for @clerk/nextjs version.

---

Still Broken? Check These Too

1. Stale browser cache: Clear cookies for your domain. Clerk sessions cached in localStorage can reference old redirectUrl. Open DevTools → Application → Cookies, delete all __clerk* entries, then hard refresh (Ctrl+Shift+R).

2. Callback URL mismatch in Clerk Dashboard: Visit [Clerk Dashboard](https://dashboard.clerk.com) → Applications → Your App → URLs. Ensure "Allowed callback URLs" includes your actual domain (e.g., http://localhost:3000/api/auth/callback for local dev). Production must use HTTPS.

3. Race condition in getAuth(): If you're manually calling getAuth() in middleware or API routes before the session is fully established, it may trigger re-authentication. Add a small delay or use clerkClient.verifyToken() instead for validation.

---

Quick Checklist

  • [ ] NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL is set to a real, protected route
  • [ ] Middleware publicRoutes doesn't include /dashboard (protected routes should be omitted)
  • [ ] Browser cookies cleared (DevTools → Application → Clear site data)
  • [ ] Callback URL in Clerk Dashboard matches your domain
  • [ ] No custom redirect logic in useRouter() that conflicts with Clerk's built-in redirect
  • ---

    Official Resources

    → [Clerk Next.js Documentation: Redirect URLs](https://clerk.com/docs/references/nextjs/overview) → [Clerk Middleware Setup Guide](https://clerk.com/docs/nextjs/middleware)

    Related guides: [Clerk session not persisting](/?guide=clerk-session-not-persisting) [Next.js middleware + authentication errors](/?guide=nextjs-middleware-auth-errors)

    ---

    Found a different variation? Drop it in the comments—we'll add it to future versions of this guide.

    🔥 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