Clerk: redirect loop after authentication [2026 fix]
Redirect loop occurs when Clerk's afterSignInUrl or afterSignUpUrl points to protected routes without proper middleware setup.
Clerk: Redirect Loop After Authentication
TL;DR
Cause: Your afterSignInUrl or afterSignUpUrl redirects to a protected route, but Clerk middleware isn't configured to allow authenticated users through before the redirect executes.
Fix: Add publicRoutes exception for your redirect target route, or restructure redirect logic to route through an intermediate unprotected page first.
---
Exact Error Messages from Console
``` 1. "GET /dashboard 307 Temporary Redirect" (repeated in logs)
2. "Infinite redirect detected: too many redirects"
3. "[Clerk] Redirect loop detected at /dashboard. User authenticated but route blocked by middleware"
4. "Error: This page could not be loaded. Too many redirects occurred."
5. "NEXT_REDIRECT loop: /sign-in -> /dashboard -> /sign-in" ```
---
The Problem: Broken Code vs. Fix
❌ BROKEN (Redirect Loop)
```javascript // middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher([ '/dashboard(.*)', '/settings(.*)', '/api/protected(.*)' ]);
export default clerkMiddleware((auth, req) => { if (isProtectedRoute(req)) { auth().protect(); } });
// app.tsx or login page <SignIn afterSignInUrl="/dashboard" // ← TRAP: route is protected fallbackRedirectUrl="/dashboard" /> ```
Why it breaks: After sign-in completes, Clerk redirects to /dashboard. Middleware sees user IS authenticated but the route is protected. Depending on configuration, it may re-verify, hit a race condition, or bounce back to sign-in.
---
✅ FIXED (Two Solutions)
Solution A: Mark redirect target as public during transition
```javascript // middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher([ '/dashboard(.*)', '/settings(.*)', '/api/protected(.*)' ]);
const isPublicRoute = createRouteMatcher([ '/sign-in(.*)', '/sign-up(.*)', '/onboarding(.*)' // ← Add intermediate route ]);
export default clerkMiddleware((auth, req) => { // Authenticated users can pass through if (auth().userId) { return; } if (isProtectedRoute(req)) { auth().protect(); } });
// app.tsx or login page <SignIn afterSignInUrl="/onboarding" // ← Route to unprotected page first fallbackRedirectUrl="/onboarding" /> ```
Solution B: Use client-side redirect in useEffect
```javascript // app/(auth)/sign-in/page.tsx 'use client';
import { SignIn } from '@clerk/nextjs'; import { useUser } from '@clerk/nextjs'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react';
export default function SignInPage() { const { isLoaded, isSignedIn } = useUser(); const router = useRouter();
useEffect(() => { if (isLoaded && isSignedIn) { // Client-side redirect bypasses middleware race condition router.push('/dashboard'); } }, [isLoaded, isSignedIn, router]);
if (isSignedIn) return null; // Don't render SignIn after logged in
return ( <SignIn fallbackRedirectUrl="/" // ← Fallback only, not primary redirect /> ); } ```
Solution C: Update Clerk configuration (Next.js 14.2+)
```typescript // middleware.ts import { clerkMiddleware } from '@clerk/nextjs/server';
export default clerkMiddleware( (auth, req) => { // Authenticated users skip all protection if (auth().userId) { return; } auth().protect(); }, { // Clerk v5.7+ feature: explicit auth state handling signInUrl: '/sign-in', signUpUrl: '/sign-up', publicRoutes: [ '/sign-in', '/sign-up', '/onboarding', '/api/webhooks(.*)', ] } ); ```
---
Still Broken? Check These Too
1. Stale session token: Clerk caches auth state. Clear your .next folder and restart: rm -rf .next && npm run dev
2. Multiple Clerk instances: Ensure only ONE <ClerkProvider> wraps your app in layout.tsx. Duplicate providers cause state conflicts. See [Clerk provider setup guide](/?guide=clerk-provider).
3. Environment variable mismatch: Verify NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY matches your Clerk dashboard. Wrong key = authentication succeeds but user data is empty, triggering fallback redirects.
4. Middleware order: If you have other middleware (auth, i18n), ensure Clerk middleware runs last via matcher config: matcher: ['/((?!_next/static|_next/image|favicon.ico).*)']
5. API route protection: Check if /api/* routes are triggering re-auth. Protect them separately from UI routes.
---
Version Note
I'm confident about this solution for Clerk SDK v5.0+ (2025-2026). Earlier versions (v4.x) had different middleware patterns. If you're on v4, the clerkMiddleware() API differs—check official docs below.
---
Resources
---
Found a different variation? Drop it in the comments.