Problem Summary
A Next.js application using Clerk for authentication is deployed to Vercel or a custom server. In development everything works: users sign in, land on the dashboard, and session cookies are set correctly. After the first production deployment, users hit the app and immediately get bounced into an infinite redirect loop. The browser shows the spinner cycling between the app URL and /sign-in, or between /sign-in and /sign-up. No error appears on screen. The network tab shows 302 responses oscillating forever until the browser throws a 'Too many redirects' error.
Symptoms
- Browser console shows 'Too many redirects' or ERR_TOO_MANY_REDIRECTS
- Network tab shows 302 responses cycling between / and /sign-in (or /sign-in and /sign-up)
- The problem only appears in production, never in local development
- Signed-in users still get redirected to /sign-in even with a valid Clerk session
- The Clerk dashboard shows no sessions being created for the affected users
- afterSignInUrl redirects back to a route that triggers the middleware again
- Some routes that should be public (like /api/webhook or /sign-up) are blocked by the middleware
- Removing the middleware.ts file temporarily stops the loop but breaks auth everywhere
Business Impact
A redirect loop at the authentication layer is a total service outage for new users. Existing users with unexpired cookies may still have access, masking the problem during internal testing. Customer-facing sign-up flows are completely broken. Every user who tries to register or log in bounces out immediately. Support tickets and churn begin within minutes of the deployment going live.
Root Cause
Clerk's Next.js SDK uses a middleware file (middleware.ts at the project root or inside /src) to protect routes. The middleware intercepts every request and checks session validity. If publicRoutes is not correctly specified, the middleware intercepts the /sign-in page itself, sees no session, and redirects to /sign-in which then repeats indefinitely. The second common cause is afterSignInUrl or afterSignUpUrl pointing to a route that the middleware also considers protected, creating a different loop where sign-in succeeds but the redirect target triggers another auth check that fails.
The most overlooked cause: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY or CLERK_SECRET_KEY not set in the production environment. When these are missing, Clerk's middleware cannot validate any session and treats every request as unauthenticated, causing all routes to redirect to sign-in including sign-in itself.
Diagnosis
Step 1: Confirm Environment Variables Are Present
Before touching any code, verify that both required Clerk environment variables exist in the production environment. Missing keys cause every symptom of the redirect loop.
vercel env lsYou must see NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY listed for the Production environment. If either is missing, add it via the Vercel dashboard under Project Settings > Environment Variables, then redeploy.
Step 2: Inspect Your middleware.ts File
Open middleware.ts at the root of your project (or src/middleware.ts). Look for how publicRoutes is configured. A misconfigured matcher or missing publicRoutes is the most common cause.
import { clerkMiddleware } from "@clerk/nextjs/server";
// BROKEN: No publicRoutes defined /sign-in is protected, causing the loop
export default clerkMiddleware();
export const config = {
matcher: ["/((?!.+\.[\w]+$)|/).*", "/", "/(api|trpc)(.*)"],
};Step 3: Fix publicRoutes to Include Auth Pages
The correct pattern explicitly marks sign-in, sign-up, and any public API routes as public. Without this, Clerk protects them and the loop begins.
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isPublicRoute = createRouteMatcher([
"/sign-in(.*)",
"/sign-up(.*)",
"/",
"/api/webhook(.*)", // Clerk webhook endpoint must be public
"/api/health",
]);
export default clerkMiddleware((auth, request) => {
if (!isPublicRoute(request)) {
auth().protect();
}
});
export const config = {
matcher: [
// Skip Next.js internals and all static files unless found in search params
"/((?!_next|[^?]*\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
"/(api|trpc)(.*)",
],
};Step 4: Check afterSignInUrl and afterSignUpUrl
If your .env.local or Clerk dashboard has afterSignInUrl set to a protected route like /dashboard, and the session cookie is not being set correctly for that domain, the user completes sign-in but immediately gets bounced back. Check these values in your environment file.
# These values control where Clerk redirects after auth
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=/onboardingIf /dashboard is protected by middleware and the session is not being committed before the redirect (an edge runtime timing issue), change NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL to / temporarily to isolate whether the loop is in sign-in itself or in the post-sign-in redirect.
Step 5: Verify Edge Runtime Compatibility
Clerk middleware runs on the Edge Runtime. If your middleware.ts imports anything that is not edge-compatible (a Node.js built-in, a database client, or a package that uses fs or net), Next.js will silently skip or break the middleware, causing unpredictable behavior.
npx next infoCheck the build output for 'middleware' warnings. If you see 'Module not found' or 'Dynamic Code Evaluation' warnings, a non-edge-compatible import is breaking your middleware. Strip the middleware.ts down to only Clerk imports and the matcher config, then re-add other imports one at a time.
Production Failure Scenarios
Scenario 1: New Deployment, Missing Secret Key
A developer copies .env.local to a new environment but forgets CLERK_SECRET_KEY. Clerk's middleware cannot validate JWT tokens without the secret. Every request is treated as unauthenticated. Every route redirects to /sign-in. /sign-in is protected (no publicRoutes). Loop starts. Fix: add CLERK_SECRET_KEY in Vercel's Environment Variables and redeploy.
Scenario 2: afterSignInUrl Points to Protected Route on Wrong Domain
The app is deployed to app.example.com but afterSignInUrl is /dashboard. The session cookie is set with domain=example.com and the middleware on app.example.com receives requests without that cookie. Every sign-in succeeds on Clerk's servers but the redirect target sees no session. Fix: ensure NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY matches the exact domain and that the Clerk application's allowed origins include app.example.com.
Scenario 3: Middleware matcher Catches Static Files
An overly broad matcher like '(.*)' causes Clerk's middleware to intercept requests for _next/static, images, and favicon.ico. These requests have no session, and if the matcher does not exclude them, they can interfere with how Clerk sets session cookies during the initial page load. The corrected matcher in Step 3 above handles this correctly.
Scenario 4: clerkMiddleware Used with Old authMiddleware API
Clerk's SDK changed its middleware export from authMiddleware (deprecated in @clerk/nextjs v5) to clerkMiddleware. If the codebase was partially upgraded, mixing old and new patterns causes unpredictable middleware behavior. Check that every import from @clerk/nextjs/server uses the current API and that there is only one middleware export in the project.
Prevention Checklist
- Always verify NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY are set in every deployment environment before going live
- Use createRouteMatcher to explicitly list public routes never rely on Clerk to guess which routes are public
- Include /sign-in(.*) and /sign-up(.*) as public routes in all Clerk middleware configurations
- Set NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL and test the redirect target is accessible to authenticated users after a cold session start
- Keep middleware.ts free of non-edge-compatible imports no database clients, no Node.js built-ins
- Test auth flow in a production-parity environment (staging with production environment variables) before every release
- Add the Clerk webhook URL (/api/webhooks/clerk or similar) to publicRoutes a protected webhook endpoint will fail silently
- Pin your @clerk/nextjs version and read the changelog before upgrading the middleware API has changed between major versions
Resolution Summary
The redirect loop was caused by a middleware.ts that did not declare /sign-in and /sign-up as public routes, combined with a missing CLERK_SECRET_KEY in the production Vercel environment. Adding createRouteMatcher with explicit public routes and supplying the missing environment variable resolved the loop immediately. The app went from a total auth outage to a working sign-in flow within one redeploy cycle.
Lessons Learned
- Never assume environment variables from local development are present in production audit them as the first diagnostic step
- Clerk's publicRoutes must explicitly include auth pages themselves, not just app content
- The Edge Runtime constraint on middleware.ts is easy to violate with a careless import and hard to debug without checking build logs
- Test the entire sign-in flow from an incognito window against a staging environment that mirrors production environment variables exactly
- The afterSignInUrl redirect is a second auth check and must target a route that the middleware considers accessible to a freshly authenticated user
Conclusion
Clerk redirect loops in production are almost always one of three things: missing environment variables, auth pages not listed in publicRoutes, or a bad afterSignInUrl target. The diagnosis takes under five minutes once you know where to look. Start with environment variables, then read middleware.ts carefully against the current Clerk SDK API, and the fix follows directly.