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 ClerkredirectUrl 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.