Clerk: redirect loop after authentication [2026 fix]

Clerk redirect loop occurs when `signInUrl` or `signUpUrl` points to protected routes. Fix: ensure auth URLs point to public pages.

Clerk: redirect loop after authentication [2026 fix]

TL;DR

Cause: Your signInUrl or signUpUrl configuration points to a route that's protected by <ProtectedRoute> or middleware, creating a loop where authenticated users get redirected back to auth.

Fix: Point signInUrl and signUpUrl to completely public, unprotected routes like /sign-in and /sign-up instead of /dashboard or authenticated pages.

---

Real Console Error Messages

``` 1. ERR_TOO_MANY_REDIRECTS: The page isn't redirecting properly

2. GET /dashboard 302 redirect → GET /sign-in 302 redirect → GET /dashboard

3. [Clerk] Redirect loop detected: signInUrl points to protected route

4. NextJS ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client

5. Infinite redirect loop: auth middleware → sign-in page → auth check → redirect again ```

---

Broken Code vs. Exact Fix

Problem 1: signInUrl Points to Protected Route

BROKEN: ```javascript // app.tsx or clerk config <ClerkProvider publishableKey={CLERK_PUBLISHABLE_KEY} signInUrl="/dashboard" signUpUrl="/dashboard/register" > <App /> </ClerkProvider> ```

FIXED: ```javascript // app.tsx - signInUrl MUST be public <ClerkProvider publishableKey={CLERK_PUBLISHABLE_KEY} signInUrl="/sign-in" signUpUrl="/sign-up" afterSignInUrl="/dashboard" afterSignUpUrl="/dashboard" > <App /> </ClerkProvider> ```

Problem 2: Middleware Protecting Auth Routes

BROKEN: ```typescript // middleware.ts export const middleware = (req: NextRequest) => { const userId = req.nextauth.session?.user?.id; if (!userId) { return NextResponse.redirect(new URL('/sign-in', req.url)); } };

export const config = { matcher: ['/((?!_next|static).*)'], // Protects EVERYTHING including /sign-in }; ```

FIXED: ```typescript // middleware.ts - exclude auth routes export const middleware = (req: NextRequest) => { const userId = req.nextauth.session?.user?.id; if (!userId) { return NextResponse.redirect(new URL('/sign-in', req.url)); } };

export const config = { matcher: [ '/((?!_next|static|sign-in|sign-up|api/auth).*)' // ^^ Explicitly exclude public auth routes ], }; ```

Problem 3: ProtectedRoute Component in Auth Flow

BROKEN: ```jsx // pages/sign-in.jsx import ProtectedRoute from '@/components/ProtectedRoute';

export default function SignIn() { return ( <ProtectedRoute> <SignInForm /> </ProtectedRoute> ); } ```

FIXED: ```jsx // pages/sign-in.jsx - NO protection wrapper import { SignIn } from '@clerk/nextjs';

export default function SignInPage() { return <SignIn />; // Clerk handles auth state internally } ```

---

Key Configuration Rules

1. signInUrl + signUpUrl must point to pages that are never protected 2. afterSignInUrl + afterSignUpUrl point to where users go *after* auth (can be protected) 3. Middleware matcher must exclude /sign-in, /sign-up, and /api/auth/* 4. Never wrap Clerk components in your own ProtectedRoute wrapper

---

Still Broken? Check These Too

1. Catch-All Routes Protecting Auth Pages

If you have app/[...slug]/page.tsx or pages/[...catch].tsx with auth logic, it will catch /sign-in requests. Use explicit routes instead or add early returns for auth paths.

2. Environment Variables Not Reloaded

After changing NEXT_PUBLIC_CLERK_SIGN_IN_URL, you must restart your dev server. Environment variables loaded at build time won't update on hot reload. Kill the process and run npm run dev again.

3. Multiple Clerk Providers (Duplicate Config)

If you have Clerk configured in both _app.tsx AND a wrapper component, conflicting signInUrl values cause loops. Keep ClerkProvider at the root only—remove from component tree. Check related guide on [Clerk configuration conflicts](/?guide=clerk-multiple-providers).

---

Version Clarification

This guide applies to:

  • @clerk/nextjs v5.0+
  • @clerk/react v4.27+
  • I am uncertain whether older v3.x versions had different redirect behavior around afterSignInUrl—check their [legacy docs](https://clerk.com/docs/upgrade-guides) if on older versions.

    ---

    Testing Your Fix

    ```bash

    1. Clear browser cache (redirect loops cache)

    Ctrl+Shift+Delete # or Cmd+Shift+Delete on Mac

    2. Test unauthenticated access

    curl -I http://localhost:3000/dashboard

    Should redirect to /sign-in, NOT loop

    3. Sign in, then verify redirect

    Should land on /dashboard, NOT /sign-in again

    ```

    ---

    See Also

  • [Clerk authentication state loops](/?guide=clerk-state-management)
  • [Next.js middleware patterns](/?guide=nextjs-middleware-auth)
  • [Official Clerk Next.js Docs](https://clerk.com/docs/quickstarts/nextjs)
  • ---

    Found a different variation? Drop it in the comments—2am bugs love company.

    🔥 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