Clerk: redirect loop after authentication [2026 fix]
Redirect loop after Clerk auth caused by misconfigured middleware or stale session redirect URL. Fix: verify CLERK_REDIRECT_URL matches your actual domain and check middleware order.
TL;DR
Cause: Your CLERK_REDIRECT_URL environment variable doesn't match your actual application domain, or Clerk middleware is catching redirects before your app logic can complete.
Fix: Set CLERK_REDIRECT_URL to your exact production domain and ensure clerkMiddleware() runs before other redirect logic.
---
Exact Console Error Messages
You'll likely see one of these in your browser console or server logs at 2am:
``` ERROR: Redirect loop detected. Clerk auth middleware redirecting to /auth/callback?redirect_url=... infinitely ```
``` TypeError: Cannot read property 'redirectUrl' of undefined at ClerkClient.verifyToken ```
``` WARNING: Session token invalid or expired. Redirecting to /sign-in, then back to /dashboard (loop) ```
``` Error: CLERK_REDIRECT_URL environment variable is not set or does not match request origin ```
``` Infinite redirect chain: /dashboard → /sign-in → /auth/callback?redirect_url=/dashboard → /dashboard (repeat) ```
---
The Problem: Broken Code
Next.js App Router (Most Common)
```javascript // ❌ BROKEN - middleware.ts (runs AFTER page redirects) import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isPublicRoute = createRouteMatcher(['/sign-in', '/sign-up']);
export default clerkMiddleware((auth, req) => { if (!isPublicRoute(req)) { auth().protect(); } });
export const config = { matcher: ['/((?!_next|static).*)'], };
// ❌ .env.local (MISSING or WRONG)
CLERK_REDIRECT_URL=http://localhost:3000 ← Points to localhost, not production!
CLERK_PUBLISHABLE_KEY=pk_live_... CLERK_SECRET_KEY=sk_live_... ```The Actual Issue
After successful authentication, Clerk tries to redirect to CLERK_REDIRECT_URL. If this URL doesn't match your actual request origin (e.g., CLERK_REDIRECT_URL=localhost:3000 but you're deployed at app.stillnotathing.com), Clerk's verification fails and it redirects back to /sign-in—creating an infinite loop.
---
The Fix: Side by Side
Step 1: Fix Environment Variables
```bash
❌ BEFORE
CLERK_REDIRECT_URL=http://localhost:3000 CLERK_PUBLISHABLE_KEY=pk_live_abc123 CLERK_SECRET_KEY=sk_live_xyz789✅ AFTER (Production)
CLERK_REDIRECT_URL=https://app.stillnotathing.com CLERK_PUBLISHABLE_KEY=pk_live_abc123 CLERK_SECRET_KEY=sk_live_xyz789 ```Important: Each environment (local, staging, production) needs its own CLERK_REDIRECT_URL. Don't use localhost in production.
Step 2: Fix Middleware Order
```typescript // ❌ BROKEN - middleware.ts import { clerkMiddleware } from '@clerk/nextjs/server';
export default clerkMiddleware();
export const config = { // Middleware runs too late; conflicts with Next.js rewrites matcher: ['/((?!api|_next/static|_next/image|favicon).*)'], }; ```
```typescript // ✅ FIXED - middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isPublicRoute = createRouteMatcher([ '/sign-in(.*)', '/sign-up(.*)', '/api/webhooks(.*)', '/', ]);
export default clerkMiddleware((auth, req) => { // Protect routes ONLY if not public if (!isPublicRoute(req)) { auth().protect(); } });
export const config = { // Ensure middleware runs early matcher: [ '/((?!_next/static|_next/image|favicon.ico|public).*)', ], }; ```
Step 3: Verify Session Handling
```typescript // ❌ BROKEN - app/dashboard/page.tsx import { auth } from '@clerk/nextjs/server';
export default async function Dashboard() { const { userId } = auth(); // Manual redirect inside page defeats middleware protection if (!userId) { redirect('/sign-in'); } return <h1>Welcome</h1>; } ```
```typescript // ✅ FIXED - app/dashboard/page.tsx import { auth } from '@clerk/nextjs/server';
export default async function Dashboard() { const { userId } = auth(); // Middleware already protected this route; trust it // Don't add redundant redirects return ( <div> <h1>Welcome, {userId}</h1> </div> ); } ```
---
Still Broken? Check These Too
1. Wrong Domain in Clerk Dashboard: Log into [dashboard.clerk.com](https://dashboard.clerk.com), go to Settings → Application URIs. Verify "Allowed redirect URLs" includes your exact production domain. [See redirect URL config guide](/?guide=clerk-redirect-urls).
2. Cookie Domain Mismatch: If using subdomains (e.g., api.stillnotathing.com + app.stillnotathing.com), Clerk cookies won't persist across subdomains. Use wildcard domain cookies or ensure auth happens on main domain.
3. Stale Build Cache: After changing .env, redeploy completely. Some platforms (Vercel, Netlify) cache old env vars. Clear build cache explicitly. [More on deployment caching](/?guide=deployment-cache).
---
Note on Version Uncertainty
This guide covers @clerk/nextjs@5.x and 6.x (2024-2025). If you're on @clerk/nextjs@4.x or earlier, the middleware API differs significantly—check [official Clerk docs](https://clerk.com/docs/references/nextjs/clerk-middleware) for your exact version.
---
Found a different variation? Drop it in the comments
Redirect loops can have edge cases (custom auth wrappers, API route issues, third-party integrations). If this didn't fix it, describe your error and we'll troubleshoot.