How to Fix: NoFallbackError on 404 routes with pages router and middleware.

6 min read

A NoFallbackError on a plain 404 route usually means Next.js is trying to resolve a request through the pages router after middleware has changed the request flow in a way that leaves the runtime without a valid fallback target. In self-hosted Next.js 14.2.7, this shows up most often when middleware rewrites, locale handling, custom asset paths, or not-found behavior interact with pages-based routing expectations.

Understanding the Root Cause

In the pages router, a request that does not match a page should eventually resolve to the built-in 404 page or your custom pages/404.js. However, middleware runs before route resolution and can rewrite, redirect, or mutate the request. If middleware rewrites a missing route to another internal destination that also cannot be resolved correctly, Next.js may attempt to continue rendering without a valid fallback response path. That is when NoFallbackError appears.

This is especially common in self-hosted deployments because production behavior depends on correct handling of:

  • x-matched-path and internal route matching semantics
  • basePath, i18n, and trailing slash normalization
  • rewrites to internal pages that do not exist for the current locale or build output
  • middleware matchers that also run on static assets, data requests, or error routes
  • custom servers or proxies that alter the original request URL

In practical terms, the bug happens when the request flow looks like this:

  1. User requests a URL that does not exist.
  2. Middleware intercepts it and rewrites or conditionally changes the path.
  3. The rewritten target is not a real page, or it conflicts with the 404 resolution path.
  4. Next.js tries to render but cannot find a proper fallback page chain.
  5. The runtime throws NoFallbackError instead of serving a clean 404.

If you are using the pages router, the safest rule is: middleware should not rewrite unknown routes into ambiguous internal destinations unless you are certain the target exists for every request shape.

Step-by-Step Solution

The fix is usually a combination of three changes: narrow your middleware matcher, avoid rewriting true missing routes unless necessary, and ensure a real 404 page exists in the pages router.

1. Add an explicit pages/404.js

Even though Next.js can serve a default 404, having an explicit page makes the not-found path predictable in self-hosted setups.

export default function Custom404() {
  return (
    <main>
      <h1>404 - Page Not Found</h1>
      <p>The page you requested does not exist.</p>
    </main>
  )
}

2. Restrict middleware so it does not run on everything

A very common cause is a matcher that also captures /_next, data requests, static files, or the 404 route itself.

import { NextResponse } from 'next/server'

export function middleware(request) {
  const { pathname } = request.nextUrl

  // Allow Next.js internals and static assets through untouched
  if (
    pathname.startsWith('/_next') ||
    pathname.startsWith('/api') ||
    pathname === '/favicon.ico' ||
    /\.[^/]+$/.test(pathname)
  ) {
    return NextResponse.next()
  }

  return NextResponse.next()
}

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

This prevents middleware from interfering with framework internals and asset delivery.

3. Avoid rewriting unknown routes to speculative destinations

If your middleware currently rewrites missing paths to another page, validate the target first or prefer a redirect only when the destination is guaranteed to exist.

Problematic pattern:

import { NextResponse } from 'next/server'

export function middleware(request) {
  const url = request.nextUrl.clone()

  if (url.pathname.startsWith('/docs')) {
    url.pathname = '/some-internal-page-that-may-not-exist'
    return NextResponse.rewrite(url)
  }

  return NextResponse.next()
}

Safer pattern:

import { NextResponse } from 'next/server'

const KNOWN_REWRITES = new Map([
  ['/docs', '/documentation'],
  ['/help', '/support'],
])

export function middleware(request) {
  const url = request.nextUrl.clone()
  const target = KNOWN_REWRITES.get(url.pathname)

  if (target) {
    url.pathname = target
    return NextResponse.rewrite(url)
  }

  return NextResponse.next()
}

Only rewrite when the destination is deterministic and built into the app.

4. Do not rewrite to /404 from middleware

In the pages router, forcing a rewrite to /404 can create confusing runtime behavior. Let Next.js naturally resolve missing routes whenever possible.

// Avoid this
return NextResponse.rewrite(new URL('/404', request.url))

Instead:

// Let route resolution handle actual missing pages
return NextResponse.next()

5. Review custom server or proxy behavior

If you are self-hosting behind Nginx, a load balancer, or a custom Node server, make sure the original path is passed through without accidental normalization that changes route matching.

// custom server example: preserve original URL
const next = require('next')
const http = require('http')

const app = next({ dev: false })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  http.createServer((req, res) => {
    handle(req, res)
  }).listen(3000)
})

If a reverse proxy strips prefixes, changes trailing slashes, or injects a mismatched base path, middleware may see a different URL than the router expects.

6. Verify basePath, locale, and trailing slash settings

When using basePath or i18n, middleware rewrites must preserve those values correctly.

/** @type {import('next').NextConfig} */
const nextConfig = {
  basePath: '/app',
  i18n: {
    locales: ['en', 'fr'],
    defaultLocale: 'en',
  },
  trailingSlash: false,
}

module.exports = nextConfig

If middleware rewrites /app/fr/missing-page to a path without the same routing context, the 404 resolution may break.

7. Use logging in middleware to confirm the final path

Before changing behavior, log exactly what middleware receives and returns.

import { NextResponse } from 'next/server'

export function middleware(request) {
  const url = request.nextUrl.clone()

  console.log('incoming pathname:', url.pathname)

  const response = NextResponse.next()

  console.log('passing through pathname:', url.pathname)
  return response
}

Then test:

  • a valid page
  • an invalid page
  • a static asset
  • a locale-prefixed invalid page
  • a page behind any reverse proxy rule

8. Upgrade if a patch version is available

If this issue maps to a framework bug in 14.2.7, test the same reproduction against the latest 14.2.x or newer stable version. Framework-level routing and middleware bugs are often fixed in patch releases. Check the Next.js release notes for routing and middleware changes.

Common Edge Cases

  • Data requests in the pages router: requests to internal JSON data endpoints may also pass through middleware. If they are rewritten incorrectly, page rendering can fail in ways that resemble 404 issues.
  • Locale-prefixed 404s: a route like /fr/unknown may fail differently from /unknown if middleware strips or duplicates the locale segment.
  • basePath mismatch: rewriting from a base-pathed request to a non-base-pathed internal path can break resolution.
  • Static export assumptions: if your deployment expects generated assets but middleware rewrites to runtime-only pages, missing fallback behavior can surface.
  • Catch-all routes: a pages/[...slug].js or pages/[[...slug]].js page may mask true 404s and interact badly with middleware rewrites.
  • Proxy normalization: upstream infrastructure that removes duplicate slashes, lowercases paths, or rewrites path prefixes can cause middleware and the pages router to disagree.

A useful diagnostic is to temporarily disable middleware and confirm that invalid routes return a normal 404. If the error disappears, the root cause is almost certainly in request mutation rather than page rendering itself.

FAQ

Why does this only happen on 404 routes and not on valid pages?

Valid pages already have a concrete route target. On a missing route, Next.js must fall back to its 404 handling path. If middleware rewrites or mutates that request into an unresolved internal destination, the runtime can lose the fallback chain and throw NoFallbackError.

Can I fix this by rewriting all missing routes to /404?

No. In the pages router, rewriting to /404 from middleware is usually not the safest approach. Let the router naturally produce a 404, and keep middleware focused on known, valid rewrites only.

Is this specific to self-hosted Next.js?

It can happen anywhere, but self-hosted environments are more exposed because custom proxies, servers, and path normalization often affect how middleware and route resolution interact. That makes fallback-related bugs easier to trigger.

The reliable fix is to keep middleware narrow, preserve routing context such as locale and basePath, avoid speculative rewrites, and let the pages router own true not-found behavior. If you still reproduce the issue after these changes, create a minimal reproduction and compare it against the latest Next.js patch release to determine whether you are hitting a framework bug rather than an application-level routing mistake.

Leave a Reply

Your email address will not be published. Required fields are marked *