nextjsseverity: can-fix
NEXT_NOT_FOUND

Next.js notFound() Error: This page could not be found

NEXT_NOT_FOUND — notFound() or missing not-found.tsx boundary

98% fixable~8 mindifficulty: beginner

Verified against Next.js 14.2 docs: not-found.js file convention, Next.js 15.0 docs: notFound() function API reference, Next.js source: packages/next/src/client/components/not-found-error.ts · Updated June 2026

> quick_fix

Either notFound() was called explicitly in a page/layout and no not-found.tsx boundary exists to catch it, or a catch-all route is swallowing URLs that should 404. Create app/not-found.tsx for the global 404 page, add segment-level not-found.tsx files where needed, and review catch-all [...slug] routes that may match unintended paths.

// app/not-found.tsx — global 404 page
import Link from 'next/link';

export default function NotFound() {
  return (
    <div>
      <h1>404 — Page Not Found</h1>
      <p>The page you are looking for does not exist.</p>
      <Link href="/">Go home</Link>
    </div>
  );
}

What causes this error

The notFound() function from next/navigation throws a special NEXT_NOT_FOUND error that Next.js catches internally to render the nearest not-found.tsx boundary. If no not-found.tsx file exists, Next.js falls back to a generic 404 page. Common issues: calling notFound() without creating a not-found.tsx file for a custom 404 UI, catch-all routes ([...slug]/page.tsx) matching URLs that should show a 404, the not-found.tsx file being placed in the wrong directory, or notFound() being called inside a try-catch block that accidentally swallows the error instead of letting it propagate to Next.js.

> advertisementAdSense placeholder

How to fix it

  1. 01

    step 1

    Create the global not-found.tsx boundary

    Place a not-found.tsx file in the app/ directory root. This file renders whenever notFound() is called from any page or when no route matches the requested URL. It must export a default React component. It automatically returns a 404 HTTP status code.

    // app/not-found.tsx
    import Link from 'next/link';
    
    export const metadata = {
      title: 'Page Not Found',
    };
    
    export default function NotFound() {
      return (
        <main className="flex min-h-screen flex-col items-center justify-center">
          <h1 className="text-4xl font-bold">404</h1>
          <p className="mt-4 text-lg">This page could not be found.</p>
          <Link href="/" className="mt-6 underline">
            Return home
          </Link>
        </main>
      );
    }
  2. 02

    step 2

    Call notFound() correctly in data fetching

    Use notFound() from next/navigation in Server Components when a database query or API call returns no data. The function throws immediately — do not wrap it in a try-catch or it will not trigger the not-found.tsx boundary.

    // app/blog/[slug]/page.tsx
    import { notFound } from 'next/navigation';
    import { db } from '@/lib/db';
    
    export default async function BlogPost({
      params,
    }: {
      params: Promise<{ slug: string }>;
    }) {
      const { slug } = await params;
      const post = await db.post.findUnique({ where: { slug } });
    
      if (!post) {
        notFound(); // throws NEXT_NOT_FOUND — renders not-found.tsx
      }
    
      return <article><h1>{post.title}</h1></article>;
    }
  3. 03

    step 3

    Fix catch-all routes swallowing 404s

    A catch-all route ([...slug]/page.tsx) matches every URL under its segment, including paths that should return 404. You must validate the slug inside the catch-all and call notFound() for invalid paths. Optional catch-all ([[...slug]]) also matches the parent segment's index, which can cause additional conflicts.

    // app/docs/[...slug]/page.tsx
    import { notFound } from 'next/navigation';
    
    const validDocs = ['getting-started', 'api-reference', 'deployment'];
    
    export default async function DocsPage({
      params,
    }: {
      params: Promise<{ slug: string[] }>;
    }) {
      const { slug } = await params;
      const docPath = slug.join('/');
    
      // Validate the slug — don't let catch-all serve any random URL
      if (!validDocs.includes(docPath)) {
        notFound();
      }
    
      return <div>Doc: {docPath}</div>;
    }
    
    // Generate static params so only valid paths are pre-rendered
    export function generateStaticParams() {
      return validDocs.map((doc) => ({ slug: doc.split('/') }));
    }
  4. 04

    step 4

    Add segment-level not-found.tsx for nested layouts

    You can place not-found.tsx at any route segment level to customize the 404 UI for that section. A not-found.tsx inside app/dashboard/ renders within the dashboard layout when notFound() is called from any dashboard page.

    // app/dashboard/not-found.tsx — 404 within the dashboard layout
    import Link from 'next/link';
    
    export default function DashboardNotFound() {
      return (
        <div>
          <h2>Dashboard page not found</h2>
          <p>The section you requested does not exist.</p>
          <Link href="/dashboard">Back to dashboard</Link>
        </div>
      );
    }

How to verify the fix

  • Visiting a non-existent URL shows the custom 404 page with a 404 HTTP status code.
  • Calling notFound() in a page renders the nearest not-found.tsx boundary.
  • The browser's network tab shows a 404 status code, not a 200.
  • Catch-all routes return 404 for invalid slugs instead of rendering empty or broken pages.

Why NEXT_NOT_FOUND happens at the runtime level

The notFound() function throws a special error object with a digest of NEXT_NOT_FOUND. The App Router catches this error during rendering and looks up the component tree for the nearest not-found.tsx boundary. If none exists, it renders the default 404 page. The HTTP response is set to 404 automatically. The common confusion arises because catch-all routes ([...slug]) match any URL path, preventing the 404 mechanism from activating — the route matches and renders with a 200 status, serving invalid paths as successful responses.

Common debug mistakes for NEXT_NOT_FOUND

  • Wrapping a notFound() call inside a try-catch block — the function works by throwing, and catching the error prevents Next.js from rendering the not-found.tsx boundary; the page renders with missing data instead of showing 404.
  • Creating a catch-all [...slug] route without validating the slug against known content — every URL under that segment returns a 200 status with empty or broken content instead of a proper 404.
  • Placing not-found.tsx inside a route group like (marketing)/not-found.tsx and expecting it to handle all routes — route groups scope the boundary, so routes outside the group will not use it.
  • Using redirect('/404') instead of notFound() — this sends the user to a page at /404 (if it exists) with a redirect status code, not a 404 status code; search engines see a redirect followed by a 200, not a proper 404.
  • Forgetting that not-found.tsx in Next.js 15 must be a Server Component by default — adding 'use client' and then trying to access server-side data inside it will fail.

When NEXT_NOT_FOUND signals a deeper problem

The not-found error often masks a deeper routing architecture problem. In large Next.js applications, the interplay between dynamic segments ([id]), catch-all segments ([...slug]), optional catch-all segments ([[...slug]]), and parallel routes (@modal) creates routing ambiguity. A URL like /products/abc/details could match app/products/[id]/details/page.tsx or app/products/[...slug]/page.tsx depending on route specificity rules. When the wrong route matches, it receives unexpected params, finds no data, and either crashes or renders empty content. The fix is not just adding notFound() — it is simplifying the routing structure so each URL has exactly one unambiguous match.

Editor's take

The 404 page is one of the most visited pages on any website, and in Next.js it is one of the most misconfigured. The basic setup is simple — create app/not-found.tsx and you are done. But the real problems start when dynamic routes enter the picture.

Catch-all routes are the primary source of 404-related bugs. A [...slug] segment matches everything, which means URLs that should 404 instead render with a 200 status code and broken content. Search engines index these garbage pages, users bookmark them, and your analytics fill with phantom pageviews. The fix is always to validate the slug against your actual content and call notFound() for anything that does not match.

The second common problem is the try-catch trap. Developers write defensive code around database queries and accidentally catch the NEXT_NOT_FOUND error that notFound() throws. The page renders with null data instead of showing the 404 boundary. Do the null check after the try-catch block, not inside it.

The HTTP status code matters more than the visual output. A page that renders 'Not Found' text but returns a 200 status is invisible to Google as a 404 — Googlebot sees a successful page with thin content and may flag it as low quality. Always use notFound() to ensure the response carries a proper 404 status code. Check with curl -I or the browser network tab.

For large applications, create segment-level not-found.tsx files in each major section (dashboard, docs, blog). This lets each section show a contextual 404 within its own layout, far better UX than dumping users to a generic root-level page.

By Bikram Nath · Curator · Updated June 2026

Frequently asked questions

What is the difference between not-found.tsx and error.tsx?

not-found.tsx handles 404 errors — routes that do not exist or data that was not found. It renders with a 404 HTTP status. error.tsx handles runtime errors (uncaught exceptions, failed API calls, component crashes) and renders with a 500 HTTP status. They are separate boundaries: notFound() triggers not-found.tsx, and thrown errors trigger error.tsx. You typically want both files in your app.

Why does my not-found.tsx render with a 200 status instead of 404?

This happens when a catch-all route matches the URL and renders the not-found content itself without calling the notFound() function. The catch-all returns a normal 200 response because from Next.js's perspective, the route matched successfully. Always use the notFound() function from next/navigation to trigger a proper 404 status code — do not manually render 404 content without calling it.

Can I use notFound() in a Client Component?

No. The notFound() function from next/navigation only works in Server Components, Route Handlers, and Server Actions. In a Client Component, it throws an error. If a Client Component needs to trigger a 404 based on client-side logic, use router.push('/not-found') or redirect to a dedicated 404 page, though this will not set the proper 404 HTTP status code.

disclosure:Errordex runs AdSense, has zero third-party affiliate or sponsored links, and occasionally links to the editor’s own paid digital products (clearly labelled). Every fix is cross-referenced against the official sources listed in the “sources” sidebar before it ships. If a fix here didn’t work for you, please email so we can update the page.