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

  • [Clerk middleware 404 errors](/?guide=clerk-middleware-404)
  • [ClerkProvider causing hydration mismatches](/?guide=clerk-hydration-fix)
  • ---

    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.

    🔥 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