Next.js not-found.js renders custom UI when a route segment calls its notFound function, such as a blog post lookup that finds no matching record. The newer global-not-found.js handles a different case: a URL that never matched any route in the app in the first place.
// app/blog/[slug]/not-found.tsx
export default function NotFound() {
return <p>Post not found.</p>;
}Placed next to a dynamic blog route, this file renders whenever that route calls its not-found function, such as after a failed lookup for the requested post.
Triggering not-found.js
The function that triggers it, imported from next/navigation, throws an error that Next.js catches and routes to the nearest matching file up the tree.
// app/blog/[slug]/page.tsx
import { notFound } from "next/navigation";
type Params = Promise<{ slug: string }>;
export default async function Page({ params }: { params: Params }) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return <article>{post.title}</article>;
}A root file placed directly in the app directory also automatically catches any URL that does not match a route at all, in addition to explicit calls inside a page. This is why most apps never need the global variant.
What the global variant adds
The global variant is an experimental convention for apps where a single layout cannot compose a consistent 404 page, most commonly when the project defines multiple root layouts or a root layout with a dynamic segment. It skips the app's normal rendering entirely, so it needs a full HTML document of its own.
// app/global-not-found.tsx
import "./globals.css";
export default function GlobalNotFound() {
return (
<html lang="en">
<body>404 - Page Not Found</body>
</html>
);
}Enabling this file requires a config flag first, since it has no effect until that flag is set. This is easy to miss during setup: the file can sit in the app directory correctly named and still be completely ignored until the corresponding flag is turned on in the config.
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: { globalNotFound: true },
};
export default nextConfig;Setting this flag and creating the file together is what activates the global fallback; adding only one of the two has no visible effect at all.
Choosing between them
| Situation | File to use |
|---|---|
| A specific lookup fails inside a route | The segment-level file |
| No single shared layout exists to compose a 404 from | The global variant |
| Every other case | A root file is enough |
Common mistake
Forgetting to import global styles and fonts inside the global variant is a common mistake, since this file bypasses the layout that normally provides them. It also only sees the operating system color scheme, so an app with an explicit light or dark toggle needs to apply that theme directly inside this file.
For a related convention that also needs its own imports because it bypasses normal rendering, see colocation in the App Router, which explains what does and does not get pulled into a route automatically. The regular not-found function pairs closely with dynamic segments, covered in dynamic routes in Next.js.
Rune AI
Key Insights
- not-found.js renders when the notFound function is called inside a route segment.
- global-not-found.js catches URLs that never matched a route in the app.
- global-not-found.js is experimental and needs the globalNotFound config flag enabled.
- global-not-found.js bypasses layouts entirely and must include its own html and body tags.
- Most apps only need a root not-found.js, not the global variant.
Frequently Asked Questions
Do I need both files in the same project?
Does global-not-found.js need to be enabled with a config flag?
Why does global-not-found.js need its own html and body tags?
Conclusion
not-found.js handles a resource that a route segment could not find at render time, while global-not-found.js catches a URL that never matched any route in the app at all. Reach for the global file only when a single layout genuinely cannot express a consistent 404 page.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.