Clerk: redirect loop after authentication [2026 fix]

Redirect loop after Clerk auth caused by misconfigured callback URL or missing middleware ordering. Fix: match CLERK_REDIRECT_URL to your actual domain and ensure ClerkMiddleware runs before other auth middleware.

Clerk: Redirect Loop After Authentication - Emergency Fix

TL;DR

Cause: Your CLERK_REDIRECT_URL environment variable doesn't match your actual deployment domain, OR ClerkMiddleware is ordered after other authentication middleware. Fix: Set CLERK_REDIRECT_URL=https://yourdomain.com in production and move ClerkMiddleware to the top of your middleware stack.

---

Exact Error Messages From Console

``` 1. ERR_TOO_MANY_REDIRECTS: The page isn't redirecting properly Location: https://yourdomain.com/auth/callback?code=xyz → https://yourdomain.com/auth/callback?code=xyz (infinite loop)

2. [Clerk] Redirect URI mismatch. Expected: https://staging.clerk.app/oauth/callback but got: https://yourdomain.com/auth/callback

3. Error: Not authenticated. Redirecting to sign-in at /sign-in?redirect_url=... → infinite redirect chain

4. [Clerk] OAuth token validation failed: redirect_uri parameter does not match registered callback URLs

5. Warning: Maximum call stack size exceeded in useAuth() hook during SSR ```

---

Broken Code vs. Fix

Problem 1: Incorrect Callback URL Configuration

BROKEN: ```javascript // .env.local CLERK_PUBLISHABLE_KEY=pk_test_xyz CLERK_SECRET_KEY=sk_test_xyz CLERK_REDIRECT_URL=http://localhost:3000 // ❌ Points to localhost in production ```

```typescript // app/middleware.ts import { authMiddleware } from "@clerk/nextjs";

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

export default authMiddleware({ publicRoutes: ["/", "/pricing"], ignoredRoutes: ["/api/public"], }); ```

FIXED: ```javascript // .env.local (local) CLERK_PUBLISHABLE_KEY=pk_test_xyz CLERK_SECRET_KEY=sk_test_xyz CLERK_REDIRECT_URL=http://localhost:3000

// .env.production CLERK_PUBLISHABLE_KEY=pk_live_xyz CLERK_SECRET_KEY=sk_live_xyz CLERK_REDIRECT_URL=https://yourdomain.com // ✅ Production domain only ```

Problem 2: Middleware Order (Next.js 13+)

BROKEN: ```typescript // app/middleware.ts import { authMiddleware } from "@clerk/nextjs"; import customAuthCheck from "./lib/customAuth";

export default authMiddleware({ publicRoutes: ["/"], });

// ❌ Custom middleware runs AFTER Clerk, causes re-authentication loop export const config = { matcher: ["/(.*)"], }; ```

FIXED: ```typescript // app/middleware.ts import { authMiddleware } from "@clerk/nextjs";

// ✅ ClerkMiddleware FIRST in the chain export default authMiddleware({ publicRoutes: ["/", "/api/webhooks(.*)"], ignoredRoutes: ["/api/webhooks/clerk"], afterAuth(auth, req) { // Custom logic runs AFTER Clerk validation if (!auth.userId && !auth.isPublicRoute) { return auth.redirectToSignIn(); } }, });

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

Problem 3: Callback URL Registration Mismatch

BROKEN: ```typescript // Clerk Dashboard shows registered callback URL: // https://yourdomain.com/auth/callback

// But code redirects to: const signInUrl = "/api/auth/callback"; // ❌ Mismatch! ```

FIXED: ```typescript // 1. In Clerk Dashboard → Applications → Your App → URLs // Ensure Callback URL = https://yourdomain.com/auth/callback

// 2. In code, use Clerk's default: import { useAuth } from "@clerk/nextjs";

function MyComponent() { const { signIn } = useAuth(); return ( <button onClick={() => signIn()}> Sign In </button> // ✅ Clerk automatically uses registered callback URL ); } ```

---

Configuration Verification Checklist

1. Verify environment variable is set: ```bash echo $CLERK_REDIRECT_URL # Should show your production domain ```

2. Check Clerk Dashboard → Your Application → URLs - Allowed Callback URLs must include your exact domain - Format: https://yourdomain.com (no trailing slash)

3. Verify middleware runs first: ```bash # If using Next.js, check middleware.ts is in root app/ or pages/ ls -la app/middleware.ts ```

4. Test locally with production URL: ```bash CLERK_REDIRECT_URL=https://yourdomain.com npm run dev ```

Note on version specificity: Clerk's middleware API changed in v4.x (2024). If you're on v3.x, use clerkMiddleware() instead of authMiddleware(). Check your package.json for @clerk/nextjs version.

---

Still Broken? Check These Too

1. [CORS/Subdomain Issues](/?guide=clerk-cors-fix) - If you're on a subdomain (api.yourdomain.com), Clerk session cookies won't transfer. Solution: Configure signInUrl and signUpUrl explicitly.

2. [useAuth Hook in SSR Context](/?guide=clerk-ssr-hydration) - Redirect loop during server-side rendering. Solution: Wrap component with <ClerkProvider> and use useRouter guards.

3. Session Persistence Failed - Clerk stores session in httpOnly cookie. If cookies are blocked: check browser Privacy/Incognito mode, ensure domain matches exactly, verify SameSite cookie settings.

---

Official Documentation

[Clerk Next.js Documentation - Environment Variables](https://clerk.com/docs/references/nextjs/env-variables) [Clerk - Redirect URIs & Callback URLs](https://clerk.com/docs/deployments/clerk-endpoints)

---

Found a different variation? Drop it in the comments. Include your Next.js version, Clerk version, and the exact error message.

🔥 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