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: YourCLERK_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.