Clerk: redirect loop after authentication [2026 fix]
Redirect loop after Clerk auth caused by misconfigured middleware or stale session state. Clear cookies and fix middleware ordering.
Clerk: Redirect Loop After Authentication - 2am Emergency Fix
TL;DR
Cause: Clerk middleware runs after your auth check redirect, or session cookies aren't being set properly due to domain/secure flag mismatch. Fix: Reorder middleware so Clerk runs first, then verifyNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY matches your domain config.---
Exact Error Messages from Console
You'll likely see one or more of these:
``` Error: Redirect loop detected. The page at 'https://yourapp.com/dashboard' redirected to 'https://yourapp.com/sign-in' which redirected back to 'https://yourapp.com/dashboard' ```
``` [Clerk] Error: Unable to load user. Session cookie not found or invalid. Code: session_not_found ```
``` warning: Clerk session validation failed at middleware layer - user object undefined after auth callback ```
``` TypeError: Cannot read property 'id' of undefined at /app/middleware.ts:15:12 ```
``` [Next.js] Infinite redirect detected - middleware condition not met after authentication ```
---
Broken Code vs. Exact Fix
Problem #1: Middleware Execution Order (Most Common)
BROKEN: ```typescript // middleware.ts - WRONG ORDER import { authMiddleware } from "@clerk/nextjs/server";
export const middleware = (req: NextRequest) => { // Your custom redirect runs BEFORE Clerk sets up the session if (req.nextUrl.pathname === "/dashboard" && !hasAuth) { return NextResponse.redirect(new URL("/sign-in", req.url)); } // Clerk runs AFTER - too late to catch the redirect return authMiddleware()(req); };
export const config = { matcher: ["/((?!_next|static).*)"], }; ```
FIXED: ```typescript // middleware.ts - CORRECT ORDER import { authMiddleware } from "@clerk/nextjs/server"; import { NextResponse } from "next/server"; import type { NextRequest } from "next/server";
export const middleware = authMiddleware((auth, req) => { // Clerk runs FIRST and populates auth state // THEN your logic runs with valid session if (req.nextUrl.pathname === "/dashboard" && !auth().userId) { return NextResponse.redirect(new URL("/sign-in", req.url)); } });
export const config = { matcher: [ // Exclude static assets and Clerk's own routes "/((?!_next|static|.*\\.png|.*\\.jpg|/sign-in|/sign-up).*)", ], }; ```
Problem #2: Environment Variable Domain Mismatch
BROKEN: ```bash
.env.local - domain doesn't match actual deployment
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xyz123 CLERK_SECRET_KEY=sk_test_abc789 NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-inMissing or wrong domain configuration in Clerk Dashboard
```FIXED: ```bash
.env.local - must match Clerk Dashboard + actual domain
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xyz123 CLERK_SECRET_KEY=sk_test_abc789 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=/onboarding ```Then in Clerk Dashboard → Settings → URLs, verify:
https://yourapp.comhttps://yourapp.com/sign-inProblem #3: Session Cookie Not Persisting
BROKEN: ```typescript // middleware.ts - doesn't preserve session const isAuth = async (req) => { const token = req.cookies.get("__session")?.value; if (!token) return false; // Session lost on redirect }; ```
FIXED: ```typescript // middleware.ts - use Clerk's native session handling import { getAuth } from "@clerk/nextjs/server";
export const middleware = authMiddleware((auth, req) => { // Clerk handles session persistence automatically const { userId } = auth(); // Session cookie managed by Clerk with correct secure/sameSite flags }); ```
---
Still Broken? Check These Too
1. Stale Browser Cache/Cookies: Open DevTools → Application → Cookies. Delete all cookies for your domain, then hard refresh (Cmd+Shift+R / Ctrl+Shift+R). Expired __session cookies cause immediate re-auth loops.
2. Next.js Build Mismatch: Run rm -rf .next && npm run build then restart. Cached build artifacts can reference old middleware logic.
3. Multiple Auth Providers Conflict: If using both Clerk AND NextAuth or Auth0, check that only ONE middleware is active. Remove conflicting authMiddleware() calls. See our [guide on combining auth providers](/?guide=multi-auth).
4. Localhost vs. Production Domain: Development works on localhost:3000 but production https://yourapp.com doesn't. Verify Clerk Dashboard has the production URL in Allowed redirect URLs. I'm uncertain if this varies by Clerk version; check your Dashboard version number before applying.
5. API Route Protected Without Middleware: If protecting /api/* endpoints, ensure middleware matcher includes them OR use getAuth() inside each route handler. See [protecting API routes](/?guide=clerk-api-protection).
---
One-Minute Diagnostic
1. Open DevTools → Network tab → filter by /sign-in or /dashboard
2. Look for redirect chains (301/302 responses back-to-back)
3. Check Network → Cookies tab for __session — should exist after sign-in
4. Compare your middleware matcher against your actual routes
5. Clear browser cookies and try again
---
Official Resources
---
Found a different variation? Drop it in the comments below — we update this guide based on your real 2am issues.