Nurture TechnologiesNurture Tech
Integrations9 min read·July 24, 2026

Clerk Authentication Redirect Loop After Deployment

Next.jsClerkVercel

A Next.js app with Clerk auth works perfectly in development but enters an infinite redirect loop in production. The root cause is almost always middleware misconfiguration publicRoutes not declared, afterSignInUrl pointing back to a protected route, or a missing environment variable in the deployment.

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 ls

You 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.

middleware.ts (broken version)
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.

middleware.ts (fixed version)
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.

.env.production
# 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=/onboarding

If /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 info

Check 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.

Nurture Technologies

NEED HELP WITH YOUR STACK?

Nurture Technologies builds and maintains production-quality software for startups and businesses. If engineering problems are slowing you down, our team can help.

Talk to our team →
FAQ

FREQUENTLY ASKED QUESTIONS

Why does the Clerk redirect loop only happen in production and not locally?+

Local development uses .env.local which typically has both Clerk keys set. Production deployments require you to manually add NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY to your hosting provider's environment variable settings. Without the secret key, Clerk cannot validate any session and every route appears unauthenticated, including /sign-in itself.

What is the difference between clerkMiddleware and the older authMiddleware?+

authMiddleware was the previous Clerk middleware export, deprecated in @clerk/nextjs v5. clerkMiddleware is the current API introduced in v5 and uses createRouteMatcher for public route declaration instead of the publicRoutes array option. Mixing the two in an upgraded codebase causes unpredictable behavior. Always check your @clerk/nextjs version and use the corresponding middleware API.

How do I make Clerk webhook endpoints bypass authentication?+

Add your webhook path to the createRouteMatcher public routes list: createRouteMatcher(['/api/webhooks/clerk(.*)']). Webhook requests come from Clerk's servers and do not carry a user session. If the middleware protects this route, Clerk's webhook delivery will fail with 401 or 302 responses, and you will lose event data silently.

Can I use Clerk middleware with the Next.js App Router and Pages Router simultaneously?+

Yes. The middleware.ts file at the project root applies to all routes regardless of router. The matcher config controls which paths are intercepted. Ensure your matcher excludes _next/static, _next/image, and static asset extensions to avoid intercepting non-page requests that have no session context.

What does 'auth().protect()' do vs. redirectToSignIn()?+

auth().protect() is the recommended pattern in Clerk v5 middleware. It automatically redirects unauthenticated users to your NEXT_PUBLIC_CLERK_SIGN_IN_URL. redirectToSignIn() is an explicit alternative that lets you customize the redirect. Using auth().protect() on a route that is already your sign-in URL creates the redirect loop always guard protect() calls with an isPublicRoute check.

How do I debug the Clerk middleware in production without deploying new code?+

Add CLERK_DEBUG=true to your production environment variables and check your hosting provider's function logs. Clerk's middleware emits detailed logs about route matching decisions, JWT validation attempts, and redirect targets when debug mode is enabled. Remove it after diagnosis as it adds log volume.

Does Clerk support multiple sign-in URLs for different user types?+

Not natively via NEXT_PUBLIC_CLERK_SIGN_IN_URL that is a single global value. For multiple user types (e.g., /admin/sign-in vs /sign-in), you need custom middleware logic that inspects the request path and calls the appropriate Clerk handler. Use createRouteMatcher to distinguish admin routes and redirect to the correct sign-in page conditionally inside clerkMiddleware.

What happens if NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY is wrong but CLERK_SECRET_KEY is correct?+

The publishable key is used client-side to initialize the Clerk frontend SDK. An incorrect publishable key means the Clerk components (SignIn, UserButton) fail to render or show a configuration error. The middleware still uses the secret key server-side for JWT validation, so middleware protection may still work while the UI is broken. Both keys must match the same Clerk application.

How do I handle Clerk session expiry without causing a redirect loop?+

Clerk handles session refresh automatically if the user is active. When a session genuinely expires, Clerk redirects to sign-in, which is correct behavior, not a loop. A loop only occurs when sign-in itself is protected. Ensure NEXT_PUBLIC_CLERK_SIGN_IN_URL is included in your publicRoutes and that the page it points to actually renders the Clerk SignIn component without a middleware redirect.

Should I commit middleware.ts changes without testing auth end-to-end in staging first?+

No. Middleware is the outermost layer of your application and a bug there breaks every route simultaneously. Always test the full sign-in, sign-up, and protected route flow in a staging environment that uses production-equivalent Clerk keys (a separate Clerk application for staging) before merging middleware changes to main.