Error and Not-Found Pages
When something breaks or a page does not exist, special error.js and not-found.js files show a friendly message instead of a crash.
What you will learn
- Catch render errors with an error.js boundary
- Show a custom 404 with not-found.js and notFound()
- Let the user retry with the reset function
Two things that go wrong in real apps
Two problems happen on every real site: something throws an error (a database is down, an API fails), and someone visits a page that does not exist (a deleted post, a typo in the URL). If you do nothing, the user sees an ugly crash screen. Next.js gives you one special file for each case.
Catching errors with error.js
Put a file named error.js in a route folder, and it becomes an error boundary — a safety net that catches any error thrown while rendering that route and shows your friendly message instead of crashing the whole app. (An error boundary is a component that "catches" errors from the components inside it, like a try/catch for your UI.)
An error.js file must be a Client Component (it needs interactivity for the retry button), so it starts with use client. Next.js hands it two props: the error that happened and a reset function that retries.
'use client';
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong.</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>Try again</button>
</div>
);
}Note: Output (when the dashboard page throws an error): Something went wrong. Failed to load sales data [ Try again ] Instead of a crash, the user sees a calm message and a button. Clicking "Try again" calls reset(), which re-renders the route — handy if the error was temporary.
Showing a custom 404 with not-found.js
A 404 means "this page does not exist". Put a not-found.js file in a route folder to design your own 404 screen for that area, instead of the plain default.
// app/blog/not-found.js
import Link from 'next/link';
export default function NotFound() {
return (
<div>
<h2>Post not found</h2>
<p>We could not find that blog post.</p>
<Link href="/blog">Back to all posts</Link>
</div>
);
}Note: Output (when a blog post is missing): Post not found We could not find that blog post. Back to all posts <- a link home Much friendlier than a blank "404". The user gets a clear message and a way back.
Triggering the 404 yourself with notFound()
Often you decide a page is missing — for example, someone visits /blog/9999 but post 9999 does not exist. Call the notFound() helper and Next.js stops rendering the page and shows your not-found.js instead.
// app/blog/[id]/page.js
import { notFound } from 'next/navigation';
export default async function PostPage({ params }) {
const post = await getPost(params.id);
if (!post) {
notFound(); // stops here and shows not-found.js
}
return <h1>{post.title}</h1>;
}Note: Output (visiting /blog/9999 when it does not exist): The page rendering stops at notFound(), and Next.js renders app/blog/not-found.js instead — your custom "Post not found" screen, with a proper 404 status code for search engines.
What happens when notFound() runs, step by step
Here is exactly what Next.js does when a visitor opens a missing post like /blog/9999:
- The visitor opens
/blog/9999. Next.js runs thePostPageServer Component on the server. - The component calls
getPost('9999'), which looks for that post and finds nothing —postis empty. - The
if (!post)check is true, so the code callsnotFound(). notFound()stops rendering the page right there — the<h1>below it never runs.- Next.js looks up the nearest
not-found.js(hereapp/blog/not-found.js) and renders it instead. - The visitor sees your friendly "Post not found" screen, and the response carries a real 404 status code so search engines treat it correctly.
Which file for which problem
| File | Handles | How it triggers |
|---|---|---|
error.js | A thrown error during render | Automatically, when code throws |
not-found.js | A missing page (404) | A bad URL, or you call notFound() |
loading.js | A page that is still loading | Automatically, while fetching |
Tip: These files are scoped like layouts. An error.js in app/dashboard/ only catches errors inside the dashboard, so the rest of your site keeps working. Put a top-level one in app/ as a catch-all safety net.
Watch out: Returning a 404 with the correct status code matters for SEO — search engines should not index a "not found" page as real content. notFound() sets that status for you, which is why it beats just rendering a "missing" message inside the normal page.
Q. A visitor requests a blog post that does not exist. What is the clean Next.js way to show a 404?
✍️ Practice
- Add an
error.jsto a route, force an error (throw in the page), and confirm the friendly screen with a "Try again" button appears. - Add a
not-found.jsto/blogand callnotFound()for a missing post id.
🏠 Homework
- Give your blog both an error.js (with a working retry button) and a not-found.js, then trigger each one to confirm they work.