Clerk: redirect loop after authentication [2026 fix]
Redirect URI mismatch between Clerk dashboard and app code causes infinite auth loops. Fix: sync your NEXT_PUBLIC_CLERK_SIGN_IN_URL with actual route handlers.
TL;DR
Cause: Your Clerk redirect URI in the dashboard doesn't match your app's actual redirect handler, causing Clerk to send users back to the login page repeatedly.
Fix: Verify NEXT_PUBLIC_CLERK_SIGN_IN_URL environment variable matches the exact path of your <SignInButton redirectUrl="/path" /> component.
---
Exact Console Error Messages
When users hit this issue, they'll see one of these exact errors:
``` Infinite redirect detected: /sign-in → /dashboard → /sign-in ```
``` [Clerk] Redirect URL mismatch: expected "/dashboard" but got "/onboarding" ```
``` Error: Clerk session is valid but user is being redirected to sign-in repeatedly ```
``` WARN [Clerk] RedirectToSignIn triggered while user session exists. Check your clerkMiddleware config. ```
``` TypeError: Cannot read property 'redirect' of undefined at clerkMiddleware ```
---
Broken Code vs. Exact Fix
Problem 1: Mismatched Redirect URLs
BROKEN: ```javascript // app/sign-in/page.tsx import { SignIn } from '@clerk/nextjs';
export default function SignInPage() { return <SignIn redirectUrl="/dashboard" />; }
// .env.local NEXT_PUBLIC_CLERK_SIGN_IN_URL=/signin NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard ```
The component says redirect to /dashboard, but Clerk's middleware is configured to send users to /signin first, creating a loop.
FIXED: ```javascript // app/sign-in/page.tsx import { SignIn } from '@clerk/nextjs';
export default function SignInPage() { return <SignIn redirectUrl="/dashboard" />; }
// .env.local NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard ```
Ensure the URL path matches exactly (note: /sign-in with hyphen if that's your actual route file structure).
Problem 2: Missing or Incorrect Middleware Configuration
BROKEN: ```typescript // middleware.ts import { clerkMiddleware } from '@clerk/nextjs/server';
export const middleware = clerkMiddleware({ publicRoutes: ['/'], });
export const config = { matcher: ['/((?!_next|.*\\..*).*)'], }; // No redirect configuration = Clerk uses defaults that may not match your app ```
FIXED: ```typescript // middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isPublicRoute = createRouteMatcher(['/','/'sign-in']);
export const middleware = clerkMiddleware((auth, req) => { if (!isPublicRoute(req)) { auth().protect(); } });
export const config = { matcher: [ '/((?!_next|static|.*\\.png|.*\\.jpg|.*\\.svg).*)', ], }; ```
This explicitly tells Clerk which routes are public, preventing unnecessary redirects.
Problem 3: Session State Not Persisting
BROKEN: ```javascript // app/dashboard/page.tsx import { auth } from '@clerk/nextjs/server'; import { redirect } from 'next/navigation';
export default async function DashboardPage() { const { userId } = await auth(); if (!userId) { redirect('/sign-in'); } return <div>Dashboard</div>; }
// Problem: auth() is called twice (server + client), sometimes returns null incorrectly ```
FIXED: ```javascript // app/dashboard/page.tsx import { auth } from '@clerk/nextjs/server'; import { redirect } from 'next/navigation';
export default async function DashboardPage() { const { userId } = await auth.protect(); // .protect() automatically redirects if not authenticated return <div>Dashboard for {userId}</div>; } ```
Use .protect() instead of checking userId manually to avoid double-redirect conditions.
---
Still broken? Check these too
1. Clerk Dashboard Mismatch: Log into [dashboard.clerk.com](https://dashboard.clerk.com), select your app, go to Settings → URLs. Verify "Sign in URL" and "Sign up URL" match your app's actual routes *exactly* (including port number if testing locally at localhost:3000).
2. Stale Environment Variables: After updating .env.local, did you restart your dev server? npm run dev doesn't always hot-reload Clerk config. Kill the process and restart.
3. Nested Layout Redirect Logic: Check parent layout.tsx files for their own <ClerkProvider> or auth checks. Multiple nested redirects compound into loops. See [related guide on ClerkProvider setup](/?guide=clerk-provider-nesting).
4. Clerk SDK Version Mismatch: If you're mixing @clerk/nextjs v4 with v5 helpers, redirect behavior changed. We can't confirm exact version-specific differences without your package.json, but if you installed Clerk 6+ months ago, run npm update @clerk/nextjs and test.
5. Browser Cache + Cookie Issues: Open DevTools → Application → Cookies. Delete all __clerk_ cookies. This forces a fresh session. Redirect loops sometimes persist in old session data.
---
Related Guides
---
Official Reference
[Clerk Next.js Redirect Documentation](https://clerk.com/docs/references/nextjs/clerk-middleware)
---
Found a different variation? Drop it in the comments—redirect loops come in many shapes, and your fix might save someone 3 hours at 2am.