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.

🔥 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