Clerk: redirect loop after authentication [2026 fix]

Clerk redirect loops occur when post-auth callback URLs don't match configured domains. Fix: verify CLERK_REDIRECT_URL and allowed origins match exactly.

Clerk: Redirect Loop After Authentication [2am Fix]

TL;DR

Cause: Your afterSignInUrl or afterSignUpUrl callback points to a domain/port that isn't registered in Clerk Dashboard's Allowed Redirect URIs.

Fix: Add your exact callback URL (including protocol and port) to Clerk Dashboard → Applications → Settings → Allowed Redirect URIs, then clear browser cache.

---

Real Console Error Messages

``` 1. "Error: Redirect uri not whitelisted" at ClerkClient.validateRedirectUri

2. "[Clerk] The redirect_uri '${uri}' is not in your instance's list of allowed redirect URIs." Error Code: CLERK_REDIRECT_NOT_ALLOWED

3. "POST /auth/callback 401 Unauthorized" response: {error: "invalid_grant", error_description: "Redirect URI mismatch"}

4. "GET /auth/verify?code=xyz - redirect loop detected" Loop count: 5, stopped at /dashboard (infinite redirect)

5. "CORS error: Origin 'http://localhost:3001' not allowed by access-control-allow-origin" (Often masked as redirect loop in browser) ```

---

Broken Code → Exact Fix

❌ BROKEN: Mismatched Callback URLs

```javascript // middleware.ts import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isPublicRoute = createRouteMatcher(['/']);

export default clerkMiddleware((auth, req) => { if (!isPublicRoute(req)) auth().protect(); });

export const config = { matcher: ['/((?!.*\..*|_next).*)'], };

// pages/dashboard.tsx import { useUser } from '@clerk/nextjs';

export default function Dashboard() { const { user } = useUser(); // Component redirects here after signin, but URL isn't whitelisted return <div>Welcome, {user?.firstName}</div>; }

// .env.local CLERK_PUBLISHABLE_KEY=pk_test_xxx CLERK_SECRET_KEY=sk_test_xxx // Missing: CLERK_REDIRECT_URL or callback configuration ```

Problem: When user signs in, Clerk tries redirecting to http://localhost:3000/dashboard but that exact URL isn't in Clerk Dashboard's whitelist. Browser loops between auth and callback indefinitely.

✅ FIXED: Explicit Redirect Configuration + Whitelist

```javascript // middleware.ts (unchanged, but now works) import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isPublicRoute = createRouteMatcher(['/']);

export default clerkMiddleware((auth, req) => { if (!isPublicRoute(req)) auth().protect(); });

export const config = { matcher: ['/((?!.*\..*|_next).*)'], };

// pages/dashboard.tsx (explicit redirect) import { useUser } from '@clerk/nextjs'; import { useRouter } from 'next/router'; import { useEffect } from 'react';

export default function Dashboard() { const { user, isLoaded } = useUser(); const router = useRouter();

useEffect(() => { if (isLoaded && !user) { // Use redirectUrl from ClerkProvider instead of relying on middleware router.push('/auth/sign-in'); } }, [isLoaded, user, router]);

if (!isLoaded) return <div>Loading...</div>;

return <div>Welcome, {user?.firstName}</div>; }

// _app.tsx (SET THIS) import { ClerkProvider } from '@clerk/nextjs';

function MyApp({ Component, pageProps }) { return ( <ClerkProvider> <Component {...pageProps} /> </ClerkProvider> ); }

export default MyApp;

// .env.local (ADD THIS) CLERK_PUBLISHABLE_KEY=pk_test_xxx CLERK_SECRET_KEY=sk_test_xxx CLERK_REDIRECT_URL=http://localhost:3000 ```

In Clerk Dashboard: 1. Go to ApplicationsSettingsAllowed Redirect URIs 2. Add exact entries: - http://localhost:3000 (development) - http://localhost:3000/dashboard (optional, but recommended) - https://yourdomain.com (production) - https://yourdomain.com/dashboard (if using explicit paths) 3. Click Save 4. Clear browser cache: Cmd+Shift+Delete or Ctrl+Shift+Delete 5. Restart dev server: stop and npm run dev

---

Still Broken? Check These Too

1. [Cookie Domain Mismatch](/?guide=clerk-cookies) — If testing across localhost:3000 and 127.0.0.1:3000, Clerk cookies won't transfer. Standardize on one.

2. Development vs Production Keys — Swapped CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY? Verify you're using pk_test_ (publishable) and sk_test_ (secret) correctly in .env.local.

3. [Clerk Org ID Configuration](/?guide=clerk-orgs) — If using Organizations feature, missing orgId in useOrganization() hook can cause silent redirects.

---

Version-Specific Notes

  • @clerk/nextjs >= 4.27.0: Redirect loop behavior changed; explicit ClerkProvider configuration now required. Earlier versions used implicit defaults.
  • Next.js 13+ App Router: Use clerkMiddleware() in middleware.ts at project root. Pages Router uses <ClerkProvider> wrapper.
  • Uncertain: Whether your Clerk instance uses legacy OAuth2 flow (pre-2024) vs current OIDC. Check Clerk Dashboard → Advanced if loops persist after trying this fix.
  • ---

    Official Resources

  • [Clerk Redirect URIs Documentation](https://clerk.com/docs/deployments/configure-allowed-redirect-uris)
  • [Clerk Next.js Integration Guide](https://clerk.com/docs/quickstarts/nextjs)
  • ---

    Found a different variation? Drop it in the comments—redirect loops can hide several root causes, and we're tracking all of them for the next update.

    🔥 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