vM.

Next.js Middleware Limits and Redirect Loops with trailingSlash

Author
Vishal Maurya
Published on
Reading time
4 min read

Overview

Two problems show up often. First, you try to use a Node library or heavy logic in middleware.ts and the build complains or behavior differs. Second, you add a redirect and the browser reports too many redirects, frequently because trailingSlash is also redirecting. Both come from misunderstanding what middleware is.

Naming and capabilities have changed across versions. Recent releases have moved toward a Node.js runtime option and a rename of the file convention, so verify details in the docs for your version.

1. What Middleware Is For

Middleware runs before a request is completed. It can rewrite, redirect, set headers, set cookies, and respond directly. It is well suited to lightweight, request-level decisions: redirecting unauthenticated users to a login route, locale detection, simple A/B routing, and adding headers.

It is not a good place for database queries, heavy computation, or authorization that must be authoritative. Treat it as an optimistic first check and verify permissions again close to the data.

2. Runtime Constraints

By default middleware runs in the Edge runtime. That means:

  • Many Node.js APIs and packages that depend on them (such as fs or some database drivers) are unavailable.
  • There are code size limits for the bundle, depending on platform.
  • Long-running work is unsuitable.

Newer Next.js versions allow selecting the Node.js runtime for middleware. If you need Node APIs, check whether your version supports that option and whether your hosting platform does. Otherwise, move the logic into a Server Component, Route Handler, or Server Action.

Middleware also cannot read or modify the response body of pages in the way a server framework middleware can; it works on requests and can set response headers or return its own response.

3. Scope It With a Matcher

By default middleware runs for many requests, including static assets. Narrow it with matcher:

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

export function middleware(request: NextRequest) {
  const hasSession = request.cookies.has('session');
  if (!hasSession && request.nextUrl.pathname.startsWith('/account')) {
    const url = request.nextUrl.clone();
    url.pathname = '/login';
    url.searchParams.set('next', request.nextUrl.pathname);
    return NextResponse.redirect(url);
  }
  return NextResponse.next();
}

export const config = {
  matcher: ['/account/:path*'],
};

Note the cookie check only proves a cookie exists, not that it is valid. Validate the session in your data layer.

4. How trailingSlash Changes Things

With trailingSlash: true in next.config.js, Next.js redirects /about to /about/. With the default false, it redirects /about/ to /about. These redirects are generated by the framework, and their interaction with middleware depends on your version, so test the actual sequence of requests.

The classic loop: middleware or an external rule normalizes URLs to remove the trailing slash, while trailingSlash: true adds it back. Each response redirects to the other form until the browser gives up.

5. Diagnose a Loop

Use curl to see the chain:

curl -sIL -o /dev/null -w '%{http_code} %{url_effective}\n' --max-redirs 5 https://example.com/about

Or inspect each hop manually without following:

curl -sI https://example.com/about | grep -i -E '^(HTTP|location)'
curl -sI https://example.com/about/ | grep -i -E '^(HTTP|location)'

If the two forms point at each other, you have two components disagreeing. Common sources: middleware, redirects() in next.config.js, a CDN or proxy rule, or a hosting platform setting.

6. Fix It With One Source of Truth

Choose one URL form and enforce it in one place. If you use trailingSlash: true, make sure your middleware paths and redirect targets include the slash, or compare against a normalized path:

// middleware.ts (normalize before comparing)
export function middleware(request: NextRequest) {
  const path = request.nextUrl.pathname.replace(/\/+$/, '') || '/';
  if (path === '/old-page') {
    const url = request.nextUrl.clone();
    url.pathname = '/new-page/'; // matches trailingSlash: true
    return NextResponse.redirect(url, 308);
  }
  return NextResponse.next();
}

Also use a redirect status that matches intent: permanent redirects for stable moves, temporary for conditional logic like login.

7. Exclude Internal Paths

Make sure the matcher does not capture _next/static, image optimization routes, or files with extensions unless you mean to. Accidentally redirecting those can look like a broken site or cause loops. Prefer an explicit allowlist of paths over a catch-all.

Conclusion

Keep middleware small, scoped, and stateless. For URL normalization, pick one owner and make every redirect target consistent with your trailingSlash setting. Test with curl against a production build rather than the dev server.

If you hit redirect loops, SEO canonicalization problems, or auth gating issues in a Next.js app, I can help audit the request flow. Contact me.

Additional Resources

  • Next.js middleware documentation
  • trailingSlash configuration
  • Redirects configuration