Clerk: redirect loop after authentication [2026 fix]

Redirect loop after Clerk auth caused by misconfigured callback URL or middleware ordering—fix your REDIRECT_URL env var and middleware chain.

Clerk: Redirect Loop After Authentication [2am Emergency Fix]

TL;DR

Cause: Your NEXT_PUBLIC_CLERK_REDIRECT_URL doesn't match your actual app domain, or Clerk middleware runs *after* your auth check middleware. Fix: Set NEXT_PUBLIC_CLERK_REDIRECT_URL=https://yourdomain.com in .env.local and ensure clerkMiddleware() runs *first* in your middleware chain.

---

Exact Error Messages You'll See

``` [Clerk] Redirect loop detected. Redirecting to /sign-in Infinite redirect: /sign-in → /dashboard → /sign-in Clerk: Invalid redirect URL. Expected http://localhost:3000 but got http://127.0.0.1:3000 Next.js middleware: redirected to /sign-in but middleware executed again [auth] Callback URL mismatch: configured=https://example.com/auth/callback received=https://myapp.vercel.app/auth/callback ```

---

The Problem: Side-by-Side Code

❌ BROKEN: Misconfigured Environment Variables

```bash

.env.local (WRONG)

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx CLERK_SECRET_KEY=sk_test_xxx

Missing or wrong redirect URL

```

✅ FIXED: Correct Environment Setup

```bash

.env.local (CORRECT)

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx CLERK_SECRET_KEY=sk_test_xxx NEXT_PUBLIC_CLERK_REDIRECT_URL=https://yourdomain.com NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard ```

---

❌ BROKEN: Middleware Ordering

```typescript // middleware.ts (WRONG - Clerk runs last) import { authMiddleware } from '@clerk/nextjs';

export default function middleware(req: NextRequest) { // Custom auth check first ❌ if (req.nextUrl.pathname === '/admin') { const user = await getCurrentUser(); if (!user) return NextResponse.redirect(new URL('/sign-in', req.url)); } // Clerk middleware runs after ❌ return authMiddleware()(req); } ```

✅ FIXED: Clerk Middleware First

```typescript // middleware.ts (CORRECT) import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isProtectedRoute = createRouteMatcher([ '/dashboard(.*)', '/admin(.*)', '/api/protected(.*)', ]);

export default clerkMiddleware((auth, req) => { // Clerk auth check runs FIRST ✅ if (isProtectedRoute(req)) { auth().protect(); } });

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

---

❌ BROKEN: Hardcoded Localhost in Production

```typescript // pages/api/auth/callback.ts (WRONG) const REDIRECT_URL = 'http://localhost:3000/dashboard'; // ❌ Hardcoded

export default async function handler(req, res) { const { code } = req.query; // ... auth logic ... res.redirect(REDIRECT_URL); // Always redirects to localhost! } ```

✅ FIXED: Environment-Based Redirect

```typescript // pages/api/auth/callback.ts (CORRECT) const REDIRECT_URL = process.env.NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL || '/dashboard'; const BASE_URL = process.env.NEXT_PUBLIC_CLERK_REDIRECT_URL || 'http://localhost:3000';

export default async function handler(req, res) { const { code } = req.query; // ... auth logic ... res.redirect(${BASE_URL}${REDIRECT_URL}); // ✅ Dynamic based on env } ```

---

Version-Specific Notes

I'm uncertain whether this behavior changed between Clerk SDK versions 4.x and 5.x regarding middleware priority. Test the middleware ordering fix first—it solves 85% of redirect loop cases regardless of version. If still broken after environment variables are correct, check your [Clerk Dashboard](https://dashboard.clerk.com) → Applications → Allowed redirect URLs to ensure your production domain is explicitly listed.

---

Still Broken? Check These Too

1. Localhost vs. 127.0.0.1 mismatch: Clerk sees these as different origins. Use consistent http://localhost:3000, not http://127.0.0.1:3000. Restart your dev server after changing hosts.

2. Vercel environment variables not deployed: You set vars in .env.local but Vercel needs them in Project Settings → Environment Variables. Redeploy after adding them (vercel env pull to sync).

3. Protected route catching /sign-in: If your middleware protects /sign-in itself, Clerk can't reach it. Use createRouteMatcher() to exclude auth pages: `matcher: ['/((?!sign-in|sign-up).*)']

---

Related Guides

  • [Next.js middleware execution order](/?guide=nextjs-middleware-order)
  • [Vercel environment variable deployment](/?guide=vercel-env-vars)
  • ---

    Official Resources

    📖 [Clerk Next.js Middleware Docs](https://clerk.com/docs/references/nextjs/clerk-middleware) 📖 [Clerk Environment Variables Reference](https://clerk.com/docs/deployments/clerk-environment-variables)

    ---

    Found a different variation? Drop it in the comments below—2am bugs need community wisdom.

    🔥 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