Clerk: redirect loop after authentication [2026 fix]

Clerk redirect loop caused by mismatched redirectUrl config or stale middleware. Fix: sync your signInUrl, signUpUrl, and afterSignInUrl across all Clerk integrations.

Clerk: Redirect Loop After Authentication – Emergency Fix

TL;DR

Cause: Your Clerk redirectUrl configuration mismatches between middleware, client component, and dashboard settings, causing post-auth redirects to re-trigger the sign-in flow.

Fix: Ensure signInUrl, signUpUrl, and afterSignInUrl point to consistent internal routes (not external URLs) and verify your middleware protect rules aren't catching authenticated users.

---

Real Console Error Messages

``` 1. GET /sign-in → GET /sign-in?redirect_url=... → GET /sign-in (infinite loop) Chrome DevTools Network tab shows repeating 307/302 redirects

2. Error: "The value provided to redirectUrl() must be a relative URL" at Clerk.setSession (clerk.browser.js:1024)

3. "Clerk: afterSignInUrl returned undefined. Ensure your Dashboard Home URL matches your application's internal routes." clerk.browser.js:542

4. TypeError: Cannot read property 'path' of undefined at AuthenticatedRoute.redirect (middleware.ts:18)

5. "Invalid redirect URL: https://yourdomain.com/dashboard was not added to your Clerk allowlist" clerk.browser.js:2156 ```

---

Broken Code → Fixed Code

Problem 1: Mismatched Redirect URLs

BROKEN: ```typescript // clerkMiddleware.ts export default clerkMiddleware(async (auth, req) => { const { userId } = await auth(); if (!userId && req.nextUrl.pathname !== '/sign-in') { return NextResponse.redirect(new URL('/sign-in', req.url)); } // Issue: No afterSignInUrl specified, Clerk defaults to // whatever's in Dashboard, which might not match your app });

// pages/sign-in.tsx import { SignIn } from '@clerk/nextjs';

export default function SignInPage() { return ( <SignIn redirectUrl="/dashboard" // ❌ Relative path OK, but... /> ); }

// Clerk Dashboard: Home URL = "https://yourdomain.com/onboarding" // ❌ Mismatch! After auth, redirects to /onboarding, // but middleware expects /dashboard ```

FIXED: ```typescript // clerkMiddleware.ts export default clerkMiddleware(async (auth, req) => { const { userId } = await auth(); // ✅ Only redirect to sign-in if NOT already there if (!userId && !req.nextUrl.pathname.startsWith('/sign')) { return NextResponse.redirect(new URL('/sign-in', req.url)); } // ✅ Explicitly set afterSignInUrl for consistency if (userId && req.nextUrl.pathname === '/sign-in') { return NextResponse.redirect(new URL('/dashboard', req.url)); } });

// pages/sign-in.tsx import { SignIn } from '@clerk/nextjs';

export default function SignInPage() { return ( <SignIn redirectUrl="/dashboard" signUpUrl="/sign-up" fallbackRedirectUrl="/dashboard" /> ); }

// Clerk Dashboard → Settings → URLs → Home URL = "/dashboard" // ✅ All three now point to /dashboard ```

Problem 2: Authenticated User Caught in Middleware Loop

BROKEN: ```typescript // middleware.ts const publicRoutes = ['/sign-in', '/sign-up'];

export default clerkMiddleware((auth, req) => { const { userId } = auth(); if (!publicRoutes.includes(req.nextUrl.pathname)) { // ❌ Bug: Checks userId AFTER Clerk middleware runs // If Clerk redirects to /sign-in, this runs AGAIN on /sign-in if (!userId) { return NextResponse.redirect(new URL('/sign-in', req.url)); } } }); ```

FIXED: ```typescript // middleware.ts const publicRoutes = ['/sign-in', '/sign-up', '/pricing'];

export default clerkMiddleware((auth, req) => { const { userId } = auth(); const pathname = req.nextUrl.pathname; // ✅ Exit early if already on public route if (publicRoutes.includes(pathname)) { return NextResponse.next(); } // ✅ Only check protected routes if (!userId) { return NextResponse.redirect(new URL('/sign-in', req.url)); } });

// matcher prevents middleware running on /sign-in repeatedly export const config = { matcher: [ '/((?!sign-in|sign-up|_next|static).*)', '/api/(.*)', ], }; ```

---

Still Broken? Check These Too

1. Stale Browser Cache: Clear cookies for your domain. In DevTools → Application → Cookies → delete all __clerk_* entries, then hard refresh (Cmd+Shift+R).

2. Environment Variables Mismatch: Verify NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY match your active Clerk instance. If you copied keys from staging, you'll get redirect mismatches. [Debugging Clerk environment variables](/?guide=clerk-env-setup).

3. Custom Domain + CNAME Issues: If using a custom domain, ensure your Clerk Dashboard → Domains has the exact domain listed. Redirect URLs must use your custom domain, not accounts.clerk.com. [Domain configuration guide](/?guide=clerk-custom-domain).

---

Version Notes

@clerk/nextjs v5.0+: The clerkMiddleware() API changed significantly. If you're on v4, the redirectUrl() function exists but behaves differently. We're documenting v5+ behavior here. If on v4, explicitly test with a small redirect test:

```typescript await clerk.redirectUrl.set('/dashboard'); ```

Exact behavior varies by minor version; if upgrading from v4 → v5, re-test all redirect flows.

---

Official Reference

[Clerk Authentication Docs – Redirect URLs](https://clerk.com/docs/references/nextjs/clerkMiddleware)

[Clerk Dashboard Configuration](https://dashboard.clerk.com)

---

Found a different variation? Drop it in the comments—we update this monthly as Clerk API evolves.

🔥 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