# Next.js 404 Page Not Found Error: Common Causes and Fixes in Next.js 13

> Fix persistent nextjs 404 page not found errors in Next.js 13. Learn common causes like incorrect routing or file structure and apply effective solutions to resolve the issue.

- Repository: [Vercel/next.js](https://github.com/vercel/next.js)
- Tags: how-to-guide
- Published: 2026-02-18

---

**Next.js returns a 404 "Page Not Found" error when the `resolveRoutes` function fails to match a request to a physical file or rewrite, triggering the fallback handler in [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts) that renders the static 404 page.**

When building applications with the [`vercel/next.js`](https://github.com/vercel/next.js/blob/main/vercel/next.js) repository, encountering a persistent **nextjs 404 page not found** error despite having defined routes is a common debugging scenario. Understanding how the framework's routing resolution works internally allows you to trace exactly where the request fails to match your expected page file.

## How Next.js 13 Handles Routing and 404 Resolution

Next.js determines the final response for every request in two distinct phases implemented in the server core:

1. **Route Resolution** – The `resolveRoutes` function in [`packages/next/src/server/lib/router-utils/resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/server/lib/router-utils/resolve-routes.ts) walks through an ordered list of routes (rewrites, redirects, middleware, static-file checks, and dynamic-route matching) to decide which **output** should handle the request.
2. **Render Fallback** – If `resolveRoutes` returns no matching output, the server falls back to the built-in 404 handling in [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts) ([`packages/next/src/server/lib/router-server.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/server/lib/router-server.ts)).

The critical logic that produces the final 404 response appears at the end of `handleRequest`:

```typescript
// packages/next/src/server/lib/router-server.ts:L66-L81
if (appNotFound) {
  return await invokeRender(parsedUrl, UNDERSCORE_NOT_FOUND_ROUTE, …);
}
await invokeRender(parsedUrl, '/404', …);

```

If `appNotFound` is false (meaning no custom `/404` page exists in the App Router) and the routing step never returned a matching output, Next.js invokes the **static 404 page**.

## Common Causes of Next.js 404 Errors Despite Defined Routes

### Incorrect File Location for Your Router Type

The most frequent cause of a **nextjs 404 page not found** error is placing page files in the wrong directory for your routing architecture. The `resolveRoutes` function checks `fsChecker.getItem(pathname)` (L71-L78 in [`resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/resolve-routes.ts)) to verify if a file exists at the expected path.

- **Pages Router**: Files must live inside `pages/` (e.g., [`pages/about.tsx`](https://github.com/vercel/next.js/blob/main/pages/about.tsx) for `/about`)
- **App Router**: Files must live inside `app/` using the [`page.tsx`](https://github.com/vercel/next.js/blob/main/page.tsx) convention (e.g., [`app/about/page.tsx`](https://github.com/vercel/next.js/blob/main/app/about/page.tsx))

If you place a file in [`app/about.tsx`](https://github.com/vercel/next.js/blob/main/app/about.tsx) instead of [`app/about/page.tsx`](https://github.com/vercel/next.js/blob/main/app/about/page.tsx), or place it in `pages/` when using the App Router, the filesystem checker returns no match, triggering the 404 fallback.

### Base Path and Asset Prefix Mismatches

When `basePath` is configured in [`next.config.js`](https://github.com/vercel/next.js/blob/main/next.config.js), the server strips this prefix before matching routes. In [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts#L21-L23), the framework normalizes the URL by removing the base path.

If you configure:

```javascript
// next.config.js
module.exports = {
  basePath: "/docs",
};

```

Then your file [`pages/help.tsx`](https://github.com/vercel/next.js/blob/main/pages/help.tsx) is reachable at `/docs/help`, not `/help`. Requesting `/help` results in a 404 because the router strips the base path (finding none), then fails to match [`help.tsx`](https://github.com/vercel/next.js/blob/main/help.tsx) against the root path.

### Internationalization (i18n) Locale Handling

For applications using the `i18n` configuration, `resolveRoutes` normalizes locale paths (L97-L135 in [`resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/resolve-routes.ts)). If the request is `/fr/about` but the page exists only under the default locale without proper locale-specific routing, the matcher may fail to resolve the file.

Ensure that:
- Locale-specific pages exist in `pages/[locale]/` or use the App Router's locale handling
- The `i18n` configuration in [`next.config.js`](https://github.com/vercel/next.js/blob/main/next.config.js) correctly maps locales to domains or paths

### Middleware Rewrites and Redirects

Middleware that rewrites requests to non-existent paths causes 404 errors after the rewrite occurs. In [`resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/resolve-routes.ts#L87-L165), middleware handling processes rewrites before filesystem checks.

If your [`middleware.ts`](https://github.com/vercel/next.js/blob/main/middleware.ts) contains:

```typescript
import { NextResponse } from 'next/server';

export function middleware(req) {
  return NextResponse.rewrite(new URL('/does-not-exist', req.url));
}

```

Every request rewrites to `/does-not-exist`, which likely has no corresponding page file. The router then falls through to the 404 handler in [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts).

### Conflicting Public Files

A file in the `public/` directory with the same path as a page file causes the router to abort with an error. According to [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts#L496), when `matchedOutput.itemPath` collides with a public asset, the framework prevents ambiguous resolution.

If you have both:
- [`public/about.html`](https://github.com/vercel/next.js/blob/main/public/about.html)
- [`pages/about.tsx`](https://github.com/vercel/next.js/blob/main/pages/about.tsx)

The router detects the conflict and fails to serve either, often resulting in a 404 or build-time error. Rename the public asset to [`public/about-static.html`](https://github.com/vercel/next.js/blob/main/public/about-static.html) to resolve the conflict.

### Trailing Slash Configuration

The `trailingSlash: true` configuration in [`next.config.js`](https://github.com/vercel/next.js/blob/main/next.config.js) requires URLs to end with a slash. The `maybeAddTrailingSlash` logic in [`resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/resolve-routes.ts#L80-L89) handles this normalization.

If trailing slash is enabled but the request omits the slash (e.g., `/about` instead of `/about/`), Next.js returns a 308 redirect to the slashed version. If the slashed version also fails to match (due to file naming issues), the user sees a 404.

## Debugging the 404 Flow in Source Code

To trace why your specific route returns **nextjs 404 page not found**, examine these key locations in the [`vercel/next.js`](https://github.com/vercel/next.js/blob/main/vercel/next.js) source:

1. **Route Resolution Entry**: [`packages/next/src/server/lib/router-utils/resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/server/lib/router-utils/resolve-routes.ts)
   - The `resolveRoutes` function iterates through rewrites, redirects, headers, middleware, and filesystem checks
   - Line 71-78: `fsChecker.getItem(pathname)` verifies physical file existence

2. **Request Handler**: [`packages/next/src/server/lib/router-server.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/server/lib/router-server.ts)
   - The `handleRequest` function orchestrates the response
   - Line 21-23: Base path stripping logic
   - Line 66-81: Final 404 fallback logic using `invokeRender(parsedUrl, '/404', …)`
   - Line 496: Public file conflict detection

3. **Path Utilities**: [`packages/next/src/shared/lib/router/utils/path-has-prefix.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/shared/lib/router/utils/path-has-prefix.ts)
   - Helper for base path and asset prefix normalization

4. **i18n Handling**: [`packages/next/src/shared/lib/i18n/normalize-locale-path.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/shared/lib/i18n/normalize-locale-path.ts)
   - Locale extraction and path normalization for internationalized routes

When debugging, add logging or breakpoints at [`resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/resolve-routes.ts) to inspect the `pathname` after normalization and verify whether `fsChecker.getItem` returns a valid item or `undefined`.

## Practical Solutions and Code Examples

### Verifying Correct Page Placement for Pages Router

For projects using the Pages Router, ensure your file structure matches the URL path exactly:

```tsx
// File: pages/blog/[slug].tsx
// URL: /blog/hello-world

import { useRouter } from 'next/router';

export default function BlogPost() {
  const router = useRouter();
  return <h1>Post: {router.query.slug}</h1>;
}

```

If you place this file in `app/blog/[slug]/page.tsx` while the project uses the Pages Router, the route will return **nextjs 404 page not found** because `resolveRoutes` searches `pages/` for the Pages Router.

### Configuring Base Path Correctly

When using `basePath`, structure your files relative to the root, but access them via the prefixed URL:

```javascript
// next.config.js
module.exports = {
  basePath: '/documentation',
  trailingSlash: true,
};

```

```tsx
// File: pages/getting-started.tsx
// Accessible at: /documentation/getting-started/

export default function GettingStarted() {
  return <div>Setup Guide</div>;
}

```

Requesting `/getting-started` without the `/documentation` prefix results in a 404 because [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts) strips the basePath (finding none), then fails to match the file.

### Resolving Middleware Rewrite Conflicts

Inspect your middleware to ensure rewrites point to valid destinations:

```typescript
// middleware.ts
import { NextResponse } from 'next/server';

export function middleware(request) {
  // Check if the rewrite target exists before rewriting
  if (request.nextUrl.pathname.startsWith('/legacy')) {
    return NextResponse.rewrite(new URL('/legacy-portal', request.url));
  }
  
  return NextResponse.next();
}

```

If `/legacy-portal` has no corresponding [`pages/legacy-portal.tsx`](https://github.com/vercel/next.js/blob/main/pages/legacy-portal.tsx) or [`app/legacy-portal/page.tsx`](https://github.com/vercel/next.js/blob/main/app/legacy-portal/page.tsx), the rewrite will trigger the 404 handler in [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts).

## Summary

- **Next.js 404 errors** occur when `resolveRoutes` in [`packages/next/src/server/lib/router-utils/resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/server/lib/router-utils/resolve-routes.ts) fails to match a request to a filesystem item, middleware rewrite, or dynamic route, forcing the fallback to [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts).
- **File location** is the most common culprit: Pages Router requires files in `pages/`, while App Router requires `app/**/page.tsx`.
- **Configuration mismatches** in `basePath`, `trailingSlash`, or `i18n` settings normalize URLs before matching, often causing unexpected 404s if file paths don't account for these prefixes.
- **Middleware rewrites** and **public file conflicts** can divert requests to non-existent paths or trigger resolution aborts before reaching your page component.
- **Debugging** requires inspecting the `pathname` after normalization in [`resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/resolve-routes.ts) and verifying `fsChecker.getItem` returns a valid output before the request reaches the 404 fallback in [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts).

## Frequently Asked Questions

### Why does my Next.js 13 app return 404 even though the file exists in the pages directory?

This typically happens when your project is configured to use the **App Router** (the default in Next.js 13+) but you placed the file in `pages/`, or vice versa. The `resolveRoutes` function in [`packages/next/src/server/lib/router-utils/resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/server/lib/router-utils/resolve-routes.ts) checks `fsChecker.getItem(pathname)` against the active router's directory. If you have [`app/page.tsx`](https://github.com/vercel/next.js/blob/main/app/page.tsx) but are looking for `/about` at [`pages/about.tsx`](https://github.com/vercel/next.js/blob/main/pages/about.tsx), the filesystem checker returns undefined, triggering the 404 fallback in [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts).

### How does the basePath configuration cause 404 errors in Next.js?

When `basePath` is set in [`next.config.js`](https://github.com/vercel/next.js/blob/main/next.config.js), the server strips this prefix from incoming URLs before attempting to match routes, as implemented in [`packages/next/src/server/lib/router-server.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/server/lib/router-server.ts) (lines 21-23). If you configure `basePath: "/docs"` but place your file at [`pages/index.tsx`](https://github.com/vercel/next.js/blob/main/pages/index.tsx), the route is only accessible at `/docs/`, not `/`. Requesting the root path results in a 404 because the router strips the basePath (finding none), then fails to locate a file matching the remaining empty path.

### Can Next.js middleware cause 404 errors even when the page file exists?

Yes. Middleware runs before the filesystem check in [`resolve-routes.ts`](https://github.com/vercel/next.js/blob/main/resolve-routes.ts) (lines 87-165). If your [`middleware.ts`](https://github.com/vercel/next.js/blob/main/middleware.ts) rewrites requests to a path that lacks a corresponding page file, the router continues through the resolution phases, finds no matching output, and falls back to the 404 handler. For example, rewriting all requests to `/maintenance` when [`pages/maintenance.tsx`](https://github.com/vercel/next.js/blob/main/pages/maintenance.tsx) does not exist will result in a 404 for every route, regardless of whether the original target pages exist.

### What is the difference between the App Router and Pages Router 404 handling?

In the **Pages Router**, 404 handling relies on [`pages/404.tsx`](https://github.com/vercel/next.js/blob/main/pages/404.tsx) or the default static 404 page, with resolution occurring through `fsChecker.getItem` looking for files in the `pages/` directory. In the **App Router**, 404 handling uses the [`not-found.tsx`](https://github.com/vercel/next.js/blob/main/not-found.tsx) file convention, and the router checks for [`app/not-found.tsx`](https://github.com/vercel/next.js/blob/main/app/not-found.tsx) or `app/[...]/not-found.tsx` when `appNotFound` is true in [`router-server.ts`](https://github.com/vercel/next.js/blob/main/router-server.ts) (lines 66-81). The App Router also supports parallel routes and intercepting routes, which add additional resolution layers not present in the Pages Router's simpler filesystem-based routing.