skip to content
Samuel Edwin's Website

Professional error and 404 handling in NextJS

/ 4 min read

When I do server side rendering which involves fetching from database or external API, I always assume that the operation can fail for a few reasons:

  • The database server was down.
  • The external API server I’m using is unreachable.

This is a minimal sample of the SSR code that can fail

async function getUser(id: string) {
throw new Error("Pretend this fails")
}
export default async function Page() {
const user = await getUser(id)
return (
<div>Name: {user.name}</div>
)
}

Next.js will show a default error page that looks like this Plain Nextjs error

Developers are used to seeing this, but non-technical users are not.
Showing default error like this can confuse non-technical users and even scare them.

Did I do something wrong that caused this?
I just paid money and this is all I got? This website is a scam!

That’s why I prefer to show a customized error page that shows a more user friendly error message. generic error

This gives our users relief that they’re not in the wrong.

Fortunately Next.js provides us a way to do this, and I’m going to show you how in this post.

Displaying custom error pages

All you need to do is to add error.tsx to handle custom error in your pages.

my-app/
├── package.json
├── node_modules
└── src/
└── app/
├── layout.tsx
├── page.tsx
└── product/
└── [id]/
├── page.tsx
└── error.tsx

The code on page.tsx remains the same, you don’t need to change anything. This keeps the pages code simple and clean.

my-app/src/app/product/[id]/page.tsx
async function getProduct(id: string) {
throw new Error("Pretend this fails")
}
export default async function Page() {
const product = await getProduct(id)
return (
<div>Name: {product.name}</div>
)
}

Any errors that are thrown in page.tsx will cause Next.js to render error.tsx instead.

Let’s see how the error.tsx should look like

'use client' // must be a client component
export default function ErrorPage({reset}) {
return (
<div>
<h2>Something went wrong</h2>
<p>There was an error showing your purchased content, please try again</p>
<button onClick={reset}>Try again</button>
</div>
)
}

This is a good place to show a user friendly error message and assure the users that everything will be fine.

I can also provide a retry button to redo the SSR operation if I want. Next.js already provided a reset function that does exactly this.

Showing 404s or non-existing resources

In a typical CRUD application, viewing a specific resource is a very common use case. For example:

  • Viewing a product detail
  • Viewing a blog post
  • Viewing a tweet
  • Viewing an Instagram post
  • And so many more

The common pattern between them is usually the url contains a specific id like /product/[productId] or /post/[postId].

I always assume that at some point a user will visit the url with an invalid id because of the following reasons:

  • Users could type or copy-pasted the wrong URL
  • The resource used to exist but not anymore.

Next.js provides a simple way to show 404 pages.

page.tsx
async function getBlogPost(id: string): Post | null {
return db.blog.findUnique(id)
}
export default async function Page() {
const post = await getBlogPost(id)
if (!post) {
notFound()
}
return (
<div>
<h2>{post.title}</h2>
<div>{post.content}</div>
</div>
)
}

When the resource is not found, the browser will get 404 response from the server and a page that looks like this. Default not found page

It works but looks off, making the site looks somewhat unprofessional.

NextJS provides a way to customize my 404 page so it blends more with my site’s design. Custom not found page

export default function NotFound() {
return (
<div>
<h2>Page not found</h2>
<p>We can't find the page you're looking for.</p>
<Link>Go to home page</Link>
</div>
);
}

Make it specific

We can utilize Next.js App Router capability to provide custom error between layouts.
This makes it easy for the users to tell where the errors are happening.

For example, I can show different errors 404 pages when visiting different urls.

my-app/
├── package.json
├── node_modules
└── src/
└── app/
├── layout.tsx
├── page.tsx
├── product/
│ └── [id]/
│ ├── page.tsx
│ ├── not-found.tsx
│ └── error.tsx
└── shop/
└── [id]/
├── layout.tsx
├── page.tsx
├── not-found.tsx
└── error.tsx

And each page shows relevant error messages instead of generic ones:

  • Shop error page shop error

  • Shop not found page shop not found

  • Product error page product error

  • Product not found page product not found

Try it for yourself

You can try it directly here and see the code here.