How to Fix: NoFallbackError on 404 routes with pages router and middleware.
A NoFallbackError on a 404 route in the Next.js pages router usually means the request path is being rewritten by middleware into a route that expects a fallback rendering flow, but the router is actually resolving a not-found state during development. The result is a confusing crash instead of a clean 404 page.
Table of Contents
Understanding the Root Cause
This issue appears when several Next.js routing mechanisms intersect:
- The app uses the pages router.
- A catch-all or optional catch-all route such as
pages/[[...slug]].tsxhandles broad URL patterns. - Middleware runs before the route is resolved and may rewrite, continue, or alter request handling.
- The request ultimately maps to a path that should return 404.
In this setup, Next.js can enter a state where the pages router believes it is dealing with a route that needs a fallback-style render, but there is no valid fallback page lifecycle available for that not-found path. That mismatch triggers NoFallbackError.
Technically, the bug is not that your 404 page is missing. The deeper problem is that middleware changes the request flow before the pages router finalizes not-found handling. With an optional catch-all route, the router can attempt to resolve the path as a dynamic page, while the framework still needs to render a proper 404. In development, this inconsistency is easier to surface as an explicit error.
Typical triggers include:
- Rewriting all requests through middleware, including paths that should be left alone.
- Using a catch-all page as a universal entry point without explicitly returning
notFound-style behavior. - Allowing middleware to process internal framework assets or special routes.
- Combining dynamic route resolution with a custom 404 strategy that the pages router cannot reconcile cleanly.
The practical takeaway is simple: do not let middleware over-handle routes that should naturally fall through to Next.js 404 resolution.
Step-by-Step Solution
The most reliable fix is to narrow middleware execution, avoid unnecessary rewrites for unknown routes, and make your catch-all page explicitly handle missing content.
1. Exclude framework and static paths from middleware
If middleware runs on everything, it may interfere with not-found resolution. Restrict it with a matcher.
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|api).*)',
],
}
This prevents middleware from touching routes that should be handled internally or bypassed entirely.
2. Avoid blanket rewrites for unknown URLs
If your middleware rewrites every request to a catch-all page, Next.js may never get a clean chance to produce a normal 404.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
if (pathname.startsWith('/known-section')) {
return NextResponse.next()
}
return NextResponse.next()
}
If you must rewrite, do it only for routes you explicitly own.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const url = request.nextUrl.clone()
if (url.pathname.startsWith('/docs')) {
url.pathname = '/docs-entry'
return NextResponse.rewrite(url)
}
return NextResponse.next()
}
3. Make the catch-all page return a true not-found result
If pages/[[...slug]].tsx is acting as a universal route, do not render empty UI for missing paths. Explicitly signal that the page does not exist.
type Props = {
page: { title: string; content: string } | null
}
export async function getServerSideProps(context) {
const slug = context.params?.slug || []
const path = Array.isArray(slug) ? slug.join('/') : ''
const page = await fetchPageByPath(path)
if (!page) {
return {
notFound: true,
}
}
return {
props: {
page,
},
}
}
export default function CatchAllPage({ page }: Props) {
return (
<main>
<h1>{page.title}</h1>
<p>{page.content}</p>
</main>
)
}
This is important because rendering a catch-all page for a missing route is not equivalent to returning a 404.
4. Add a dedicated 404 page
Even when using a catch-all route, a proper pages/404.tsx helps ensure the framework has a stable not-found target.
export default function Custom404() {
return (
<main>
<h1>404 - Page Not Found</h1>
<p>The page you requested does not exist.</p>
</main>
)
}
5. If possible, remove middleware from routes intended to 404
For debugging, temporarily disable middleware matching for the affected route pattern. If the error disappears, that confirms the route interception is the cause.
export const config = {
matcher: ['/docs/:path*'],
}
This targeted matcher is often enough to stop NoFallbackError on unrelated URLs.
6. Validate behavior in development and production
Some routing bugs are more visible in development. After applying the fix:
- Run the app locally in dev mode.
- Visit known routes.
- Visit invalid routes.
- Confirm invalid routes render your 404 page instead of throwing.
- Test after a production build as well.
npm run dev
npm run build
npm run start
Recommended implementation pattern
If you need middleware plus a catch-all page, use this pattern:
- Middleware only for well-defined path groups.
- Catch-all route only for content-backed pages.
- notFound: true for unknown content.
- pages/404.tsx as the final not-found renderer.
Common Edge Cases
Middleware rewrites to a dynamic route that has no data
If middleware rewrites to a dynamic page but that page cannot resolve data, returning incomplete props instead of notFound: true can surface confusing routing errors.
Optional catch-all routes swallowing too much
[[...slug]] matches both the root path and nested paths. If you use it as a universal page, make sure the root case and unknown path case are handled separately and intentionally.
Custom server logic conflicting with middleware
If your app also has reverse proxy rules, edge logic, or host-based routing, a request may already be transformed before Next.js middleware runs. That can make 404 debugging harder.
Internal paths accidentally intercepted
Always exclude /_next, image optimization routes, and other framework-managed paths. Intercepting them can produce unrelated runtime failures that look like page routing bugs.
Using rewrites where redirects are more appropriate
A rewrite keeps the original URL while changing the internal destination. If the user should truly land somewhere else, a redirect may be safer and less confusing for route resolution.
Differences between pages router and app router behavior
This issue is specific to the pages router flow. If you are reading newer documentation, be careful not to mix app router concepts such as special not-found boundaries with pages-based APIs.
FAQ
Why does this error show up mainly on 404 routes?
Because the failure happens at the intersection of dynamic route matching, middleware interception, and the frameworkâs not-found rendering path. Valid routes usually resolve before that mismatch appears.
Can I keep using pages/[[...slug]].tsx?
Yes. The key is to make it return notFound: true when content does not exist, and to avoid middleware patterns that force every unknown URL through that page.
Should I replace rewrites with redirects?
Only when the destination should be visible to the browser. If you need an internal destination change, keep the rewrite, but apply it only to explicit route groups and not to unknown paths that should 404 naturally.
In short, the fix is to stop middleware from hijacking not-found requests and to ensure your catch-all page returns a real 404 when data is missing. Once those two pieces are aligned, the pages router can render 404 routes normally without throwing NoFallbackError.