Handling 404 Not Found Pages in Next.js App Router
Learn to implement custom 404 pages in Next.js using not-found.js. Improve your site's UX by gracefully handling missing routes and missing data.

Previously in this course, we explored handling errors with error.js, which manages runtime exceptions in your application. While error.js catches unexpected crashes, sometimes the "error" isn't a bug—it’s simply that the requested resource doesn't exist.
In this lesson, we’ll implement custom 404 Not Found pages. Providing a helpful, branded, and intuitive experience for missing pages is a cornerstone of professional UX, ensuring users don't feel lost when they navigate to a broken link.
Understanding the 404 Lifecycle
In the Next.js App Router, there are two primary ways a user might end up on a "Not Found" page:
- Automatic: The user visits a URL that does not match any route defined in your
appdirectory. - Programmatic: The user visits a valid route (like
/blog/my-post), but the underlying data (the post) is missing from your database.
Next.js provides a specialized file, not-found.js, to handle both scenarios consistently.
Implementing not-found.js
The not-found.js file works similarly to layout.js or page.js. When placed in your directory structure, it acts as a global (or scoped) fallback.
1. Creating the Global Not Found Page
Create a file named app/not-found.js in the root of your project. This will catch any route that doesn't exist in your application.
JSXimport Link from CE9178">'next/link'; export default function NotFound() { return ( <div className="flex flex-col items-center justify-center min-h-[50vh] text-center"> <h2 className="text-4xl font-bold mb-4">404 - Page Not Found</h2> <p className="mb-8 text-gray-600"> Oops! We couldn't find the page you were looking for. </p> <Link href="/" className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700" > Return Home </Link> </div> ); }
2. Triggering 404 Programmatically
Sometimes, a route exists, but the data does not. For example, if you have a dynamic route /blog/[slug], the route segment exists, but a specific slug might not match any record in your database.
To trigger the "Not Found" UI from a Server Component, you use the notFound function from next/navigation.
JSX// app/blog/[slug]/page.js import { notFound } from CE9178">'next/navigation'; export default async function BlogPost({ params }) { const post = await getPostFromDatabase(params.slug); if (!post) { // This triggers the nearest not-found.js notFound(); } return <h1>{post.title}</h1>; }
When to use 404 vs. Error Boundaries
It is important to distinguish between these two concepts to keep your code clean and your users happy.
| Scenario | Tool | Why? |
|---|---|---|
| Route/Data Missing | not-found.js | Expected behavior for invalid user input. |
| Unexpected Crash | error.js | Unexpected runtime exceptions (e.g., DB connection loss). |
Hands-on Exercise
- Create the file: Add
app/not-found.jsto your project root. - Style it: Use the Tailwind CSS classes we learned in our previous lesson to match your blog's aesthetic.
- Test the trigger:
- Visit a non-existent URL (e.g.,
localhost:3000/broken-link) to see your global 404 page. - Go to your dynamic blog post page (
/blog/[slug]) and force anotFound()call when a dummy slug (like/blog/does-not-exist) is provided to see it in action.
- Visit a non-existent URL (e.g.,
Common Pitfalls
- Forgetting
not-found.jsis not a catch-all for errors: Do not usenotFound()to handle API failures or database timeouts.notFound()is specifically for "The resource exists in the real world, but not in our database." - Missing Imports: Ensure you import
notFoundfromnext/navigation, notnext/router. - Layout Conflicts: Remember that
not-found.jswill be rendered inside your rootlayout.js. If you have a complex sidebar or header, it will remain visible—which is usually the desired behavior for a consistent UX.
FAQ
Can I have multiple not-found pages?
Yes. You can place not-found.js inside specific route segments (e.g., app/blog/not-found.js) to provide custom messaging specific to that section of your site.
Does notFound() stop execution?
Yes. When you call notFound(), it throws an error that aborts rendering for that specific route segment, ensuring no further code in your component executes.
Is this related to technical SEO?
Absolutely. Properly returning a 404 status code (which Next.js handles automatically when you use not-found.js) is essential for fixing basic technical issues and maintaining a healthy site index.
Recap
In this lesson, we added a layer of professionalism to our blog by handling missing content. We implemented not-found.js for automatic routing errors and leveraged notFound() for data-specific missing resources. These practices ensure your users always have a clear path forward, even when they reach a dead end.
Up next: We will begin building reusable UI components to keep our blog codebase DRY and maintainable.
Work with me

Next.js Full-Stack Web App Development
A fast, SEO-ready full-stack web app built with Next.js 16 — from idea to deployed product, by an engineer who ships to production.

Next.js E-commerce Store Development
Turn your Facebook page or small shop into a real online store — fast, mobile-first, and built to sell. Own your storefront, not just a social page.
