Fix Next.js Middleware Redirect Loop: Root Causes and Minimal Checks

Topic: nextjs-middleware-redirect-loop-fixUpdated 7/23/2026

Quick Answer

  • Conclusion: Middleware redirect loops occur when the redirect condition is met even after the redirect has already been applied, causing the browser to cycle indefinitely. The fix is to add an exclusion check for the target path before redirecting.
  • First checks: Verify that your middleware.ts file is in the project root (same level as pages/ or app/, or inside src/), and that your matcher configuration is a static constant array with paths starting with /.
  • Minimal fix: Before calling NextResponse.redirect(), check request.nextUrl.pathname to ensure the current path is not already the redirect destination. For example, if (request.nextUrl.pathname === '/home') return NextResponse.next().
  • Applicable environment: Next.js 14 Pages Router (not App Router). Middleware runs on Edge Runtime, not Node.js.

What Problem It Solves

Next.js Middleware allows you to run code before a request completes, enabling authentication checks, redirects, rewrites, A/B testing, internationalization (i18n), and logging. However, a common pitfall is creating an infinite redirect loop where the Middleware redirects a request to a path, and then the same Middleware logic redirects that path again, causing the browser to report ERR_TOO_MANY_REDIRECTS. This article explains how to identify, debug, and fix redirect loops in Next.js 14 Pages Router Middleware.

When This Error or Setup Appears

The redirect loop error typically appears in these scenarios:

  • Authentication redirects: Redirecting unauthenticated users to a login page, but the login page itself triggers the same redirect condition.
  • i18n redirects: Redirecting users based on Accept-Language header, but the redirected path still matches the redirect rule.
  • Role-based routing: Redirecting users to different dashboards based on their role, but the dashboard path itself triggers another redirect.
  • Custom domain or subdomain handling: Redirecting based on hostname, but the target hostname also matches the redirect condition.

Minimal Working Configuration

Place this middleware.ts file in your project root (same level as pages/ or inside src/):

TYPESCRIPT
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Check if the user is authenticated (example: check for a cookie)
  const isAuthenticated = request.cookies.has('session');
  const { pathname } = request.nextUrl;

  // CRITICAL: Prevent redirect loop by checking the target path
  if (pathname === '/login') {
    return NextResponse.next();
  }

  if (!isAuthenticated) {
    const loginUrl = new URL('/login', request.url);
    return NextResponse.redirect(loginUrl);
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

The matcher above excludes API routes, static assets, and favicon from Middleware execution. Adjust it to match your project's needs.

Root Cause Analysis

A redirect loop occurs because the Middleware function runs on every request that matches the matcher configuration. When you redirect to a path (e.g., /login), the browser makes a new request to that path. If the Middleware does not exclude that path from the redirect logic, it will redirect again, creating an infinite cycle.

Common patterns that cause loops:

PatternProblemFix
Redirect all unauthenticated users to /loginThe /login page itself is unauthenticated, so it redirects to itselfCheck pathname !== '/login' before redirecting
Redirect based on cookie valueThe redirected path also matches the cookie-based conditionAdd an early return for the target path
Redirect based on hostnameThe target hostname also triggers the same redirectUse a different condition or exclude the target hostname

Execution order matters: next.config.js redirects run before Middleware. If a static redirect in next.config.js conflicts with your Middleware logic, the static redirect takes precedence. Middleware will not execute for paths already handled by redirects.

Common Errors and Fixes

Error 1: Redirect Loop (ERR_TOO_MANY_REDIRECTS)

Solution: Add a guard clause that returns NextResponse.next() for the redirect destination path before the redirect logic.

TYPESCRIPT
// BAD - causes loop
if (!isAuthenticated) {
  return NextResponse.redirect(new URL('/login', request.url));
}

// GOOD - prevents loop
if (pathname === '/login') {
  return NextResponse.next();
}
if (!isAuthenticated) {
  return NextResponse.redirect(new URL('/login', request.url));
}

Error 2: Middleware Not Executing

Solution: Verify these conditions:

  • middleware.ts is in the project root (not inside pages/ or app/)
  • matcher paths start with / and are static constants (no variables)
  • Clear Next.js cache: delete the .next directory and rebuild
BASH
rm -rf .next
npm run build

Error 3: TypeError: Cannot read properties of undefined (reading 'get')

Solution: Ensure the request parameter is typed correctly. In TypeScript, import NextRequest from next/server:

TYPESCRIPT
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Now request.cookies and request.nextUrl are properly typed
}

Error 4: Edge Runtime Node.js API Errors

Solution: Middleware runs on Edge Runtime, which does not support Node.js native modules (fs, path, crypto with Node.js API, Buffer). Move any Node.js-dependent logic to API routes or getServerSideProps. Use Web APIs instead:

  • Use fetch() instead of fs.readFile()
  • Use TextEncoder/TextDecoder instead of Buffer
  • Use Web Crypto API instead of crypto module

Production Notes and Security Checks

Critical Limitations

LimitationImpactWorkaround
Edge Runtime onlyNo fs, path, Buffer, or Node.js cryptoMove Node.js logic to API routes
matcher must be staticCannot use dynamic values or environment variablesDefine all paths statically in the array
Request header size limitLarge cookies or custom headers cause 431 errorsKeep headers under 8KB
Execution order with redirectsStatic redirects in next.config.js run firstExclude conflicting paths from redirects

Security Best Practices

  1. Never expose API keys or secrets in Middleware code. Use environment variables only in server-side code (API routes, getServerSideProps).
  2. Validate authentication tokens securely using Web Crypto API or a lightweight JWT library that supports Edge Runtime (e.g., jose).
  3. Avoid expensive operations in Middleware. Keep execution time under 50ms to prevent request delays.
  4. Use waitUntil for background tasks (Next.js 14+): event.waitUntil(promise) allows non-blocking operations like logging or analytics without delaying the response.

Debugging Tips

  • Add temporary logging to trace path changes:
    TYPESCRIPT
    console.log('Middleware path:', request.nextUrl.pathname);
    
  • Use NextResponse.rewrite() instead of redirect() during development to avoid browser redirect loops while testing.
  • Check the browser's Network tab for the redirect chain to identify which paths are involved.

FAQ

Q: How do I implement role-based redirects in Middleware?

A: Parse the JWT token or session cookie to extract the user's role. Use Web APIs (not jsonwebtoken) for decoding. Example:

TYPESCRIPT
const token = request.cookies.get('token')?.value;
if (token) {
  const payload = JSON.parse(atob(token.split('.')[1])); // Decode JWT payload
  if (payload.role === 'admin' && pathname.startsWith('/dashboard/user')) {
    return NextResponse.redirect(new URL('/dashboard/admin', request.url));
  }
}

Q: What is the priority between Middleware and next.config.js redirects?

A: next.config.js redirects execute before Middleware. If a static redirect matches a path, Middleware will not run for that path. To have Middleware override redirects, exclude the relevant paths from redirects or handle all logic in Middleware.

Q: How do I handle i18n redirects without causing loops?

A: Check if the path already contains a language prefix before redirecting:

TYPESCRIPT
const hasLangPrefix = /^\/(en|fr|de|ja)/.test(pathname);
if (!hasLangPrefix) {
  const lang = request.headers.get('accept-language')?.split(',')[0]?.split('-')[0] || 'en';
  return NextResponse.redirect(new URL(`/${lang}${pathname}`, request.url));
}

Official References

Related Guides