Clerk: redirect loop after authentication [2026 fix]
Clerk redirect loop caused by mismatched redirect URIs or missing CLERK_SIGN_IN_URL env var. Fix: update environment variables and Next.js middleware config.
Clerk: Redirect Loop After Authentication [2am Emergency Fix]
TL;DR
Cause: YourCLERK_SIGN_IN_URL environment variable doesn't match your actual sign-in route, or Clerk middleware is redirecting before checking authentication state.
Fix: Set CLERK_SIGN_IN_URL=http://localhost:3000/sign-in (or production equivalent) and add publicRoutes to your middleware config.---
Real Console Error Messages
``` Error: Redirect loop detected. Redirecting from /sign-in to /sign-in ```
``` UnhandledPromiseRejection: Error: The URL "undefined" is invalid. Check your CLERK_SIGN_IN_URL environment variable ```
``` Next.js redirect loop: ERR_TOO_MANY_REDIRECTS at /sign-in (HTTP 310) ```
``` Clerk: User is not authenticated, but redirect destination is also protected. This creates a loop. ```
``` TypeError: Cannot read property 'redirectUrl' of undefined in clerkMiddleware ```
---
The Problem: Side-by-Side Code Comparison
❌ BROKEN CODE
.env.local (missing or incomplete):
```env
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_abc123
CLERK_SECRET_KEY=sk_test_xyz789
Missing CLERK_SIGN_IN_URL!
```middleware.ts (too restrictive):
```typescript
import { clerkMiddleware } from '@clerk/nextjs/server';
export default clerkMiddleware();
export const config = { matcher: ['/((?!_next|static|public).*)'], }; ```
✅ EXACT FIX
.env.local (corrected):
```env
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_abc123
CLERK_SECRET_KEY=sk_test_xyz789
CLERK_SIGN_IN_URL=/sign-in
CLERK_AFTER_SIGN_IN_URL=/dashboard
CLERK_AFTER_SIGN_OUT_URL=/
```
middleware.ts (with publicRoutes):
```typescript
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher(['/dashboard(.*)', '/api/protected(.*)']);
export default clerkMiddleware((auth, req) => { if (isProtectedRoute(req)) auth().protect(); });
export const config = { matcher: [ '/((?!_next|static|public|sign-in|sign-up).*)', ], }; ```
---
Why This Happens
Root cause chain:
1. User hits /dashboard (protected route)
2. Middleware sees no auth token → tries to redirect to sign-in URL
3. CLERK_SIGN_IN_URL is undefined → defaults to /sign-in
4. Middleware *also* protects /sign-in → redirects back to itself
5. Browser sees infinite redirect chain → error
OR:
1. CLERK_SIGN_IN_URL=/ (root)
2. User authenticates → redirects to /
3. Middleware checks / → sees it's protected → redirects to sign-in again
4. Loop persists
---
Critical Configuration Details
For Next.js 13.4+ (App Router):
/sign-in (relative paths work fine)publicRoutes handles this automatically in newer Clerk versionsFor Next.js 12 or Pages Router:
CLERK_SIGN_IN_URL=https://yourdomain.com/sign-inpackage.json—check your Clerk package version---
Still Broken? Check These Too
1. Stale cache + env vars not loaded: Kill your dev server (Ctrl+C), clear .next folder, restart npm run dev. Environment variables don't hot-reload.
2. Clerk API rate limiting: If you're seeing "too many requests" in Network tab, you're stuck in a loop. Clear cookies (Application → Cookies → delete all __clerk_* entries), hard refresh (Ctrl+Shift+R).
3. Conflicting auth libraries: If you have NextAuth.js or Auth0 middleware also active, they can fight Clerk's middleware. Disable competing auth in your middleware.
4. Sign-in page doesn't exist: Create /app/sign-in/page.tsx with:
```typescript
import { SignIn } from '@clerk/nextjs';
export default function SignInPage() {
return <SignIn />;
}
```
5. Trailing slashes mismatch: CLERK_SIGN_IN_URL=/sign-in/ (with slash) won't match matcher for /sign-in (without). Remove trailing slashes.
---
Verification Checklist
.env.local has all three variables: PUBLISHABLE_KEY, SECRET_KEY, SIGN_IN_URL/sign-in route is NOT in middleware's protected routes listCLERK_SIGN_IN_URL value matches your actual sign-in page path<SignIn /> component---
Related Issues
---
Official Resources
---
Found a different variation? Drop it in the comments—variations include Remix/Express setups, custom domain issues, or multi-tenant configurations. Your 2am fix might save someone else's deploy.