30
Create a custom error component in Next.js (SSR & CSR)
Next.js comes with great support for error handling. In the following article, I am going to show you how to implement a personalized error component by building a tiny application that fetches data from the Rick and Morty API and can handle errors both on the server-side and the client-side.
If you want to go straight to the code, here is the repo: Next.js Error Component
Let's dive right in:
Let's dive right in:
Feel free to skip this part if you already have an application up and running that throws some errors ๐
npx create-next-app@latest custom-error-component
npm run dev
inside that newly created directory and inspecting the Next.js default page on localhost:3000
import Link from 'next/link';
export default function Home() {
return (
<div className="home-container">
<h2>Welcome to my amazing Rick and Morty Page!</h2>
<div className="img-container">
<img src="https://rickandmortyapi.com/api/character/avatar/2.jpeg"></img>
<img src="https://rickandmortyapi.com/api/character/avatar/1.jpeg"></img>
</div>
<div className="link-container">
<Link href="/characters">
<a>
Show me Rick and Morty Characters!
</a>
</Link>
<Link href="/locations">
<a>
Show me Rick and Morty locations!
</a>
</Link>
</div>
</div>
)
}
For the CSS, just grab this and copy it into the globals.css file:
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
/* styles for index.js */
.home-container {
min-height: 100vh;
display: flex;
flex-direction: column;
gap: 2rem;
justify-content: center;
}
.home-container h2 {
text-align: center;
}
.img-container {
display: flex;
gap: 2rem;
justify-content: center;
}
a {
border: 1px solid black;
padding: 0.6rem 1rem;
border-radius: 5px;
}
.link-container {
display: flex;
justify-content: center;
gap: 2rem;
}
/* styles for locations.js */
.locations-container {
max-width: 1100px;
margin: auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
margin-block: 2rem;
}
.locations-container a {
align-self: baseline;
justify-self: baseline;
}
.locations-container a:nth-of-type(2) {
justify-self: end;
}
.locations-container h2 {
text-align: center;
}
article {
border: 1px solid black;
border-radius: 5px;
padding: 0 1rem;
}
/* styles for characters.js */
.character-card {
padding: 1rem;
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
}
/* styles for the error page */
.error-container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
import React, { useEffect, useState } from 'react';
import Link from 'next/link'
function Locations(rops) {
const [locations, setLocations] = useState({});
useEffect( () => {
fetch("https://rickandmortyapi.com/api/location")
.then(res => res.json())
.then(data => setLocations(data));
}, [])
return (
<div className="locations-container">
<Link href="/"><a>Back to home</a></Link>
<h2>Rick and Morty Locations</h2>
<Link href="/characters"><a>Show me Rick and Morty Characters!</a></Link>
{
locations.results?.map((location) => (
<article key={location.id}>
<p>Name: {location.name}</p>
<p>Dimension: {location.dimension}</p>
<p>Type: {location.type}</p>
</article>))
}
</div>
);
}
export default Locations;
Please go ahead and improve this visually if your design skills are better than mine, but I didn't want to write too much CSS and distract from the actual topic.
Note the ? in locations.results?.map - This is called optional chaining. If the data takes a while to be fetched, React will try to map over locations.results but there will be no locations.results yet and our application will break. With conditional chaining, React will not try to iterate if there is no data yet, and just display the title and the buttons.
import React from 'react';
import Link from 'next/link'
function Characters(props) {
return (
<div className="locations-container">
<Link href="/"><a>Back to home</a></Link>
<h2>Rick and Morty Characters</h2>
<Link href="/locations"><a>Show me Rick and Morty Locations!</a></Link>
{
props.characters.results.map( (character) => (
<article key={character.id} className="character-card">
<img src={character.image} alt={character.name} height="200px" />
<div>
<p>{character.name}</p>
<p>{character.location.name}</p>
</div>
</article>
))
}
</div>
);
}
export default Characters;
export async function getServerSideProps(context) {
const characters = await fetch("https://rickandmortyapi.com/api/character").then(res => res.json());
return { props: { characters}}
}
The function getServerSideProps will get called before the component is mounted. Then, it will pass the data via props to the component and render it out. We won't be able to see the request in the network tab of our development tools, because the development server is doing the fetching before sending the page to our browser. We also don't need any conditional chaining here since there won't be a moment where the component is waiting for data.
Beautiful! We can now navigate around and everything works just fine. But what happens if the Rick and Morty API changes or breaks? Let's try:
You might think, we have a little problem here: The Rick and Morty API is not under our control, so we can't break it or force it to return errors to us. That's true and I chose the API on purpose because of that. We will have to get creative to simulate some errors:
location
in the API call for something else, like for example locaon
character
with charter
or something else that does not exist.npm run build
to create a production buildnpm run start
to start that production build locally.

Let's fix this:
First of all, stop the production build and return to your hot-reloaded dev build.
To create a custom error component, we have to create a file called
To create a custom error component, we have to create a file called
_error.js
in the pages folder. You can find the documentation about this page in the Next.js docs: Customized Error Component. Copy and paste the code from the docs and adapt it to go with the look and feel of your application. Mine looks like this:import Link from 'next/link';
function Error({ statusCode }) {
return (
<div className="error-container">
<img src="https://rickandmortyapi.com/api/character/avatar/234.jpeg" alt="a dead morty..."/>
{statusCode && <h1>Error: {statusCode}</h1>}
<p>We are sorry! There was an error</p>
<Link href="/">
<a>Go back home</a>
</Link>
</div>
)
}
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
return { statusCode }
}
Now how do we show it?
Let's look at
With this, I can conditionally render the Error component I just created:
Let's look at
locations.js
first. Leave the typo we introduced earlier and call https://rickandmortyapi.com/api/locaon
instead of location, we will get back an object looking like this: { error: 'There is nothing here'}
.With this, I can conditionally render the Error component I just created:
const [locations, setLocations] = useState({});
useEffect( () => {
fetch("https://rickandmortyapi.com/api/locaon")
.then(res => res.json())
.then(data => setLocations(data));
}, [])
if(locations.error) {
return <Error />
}
return (
<div className="locations-container">
<Link href="/"><a>Back to home</a></Link>
Now fix the URL and see how the original page reappears. ๐
How can we tackle the server-side rendered page? We need to take into account that the Next.js documentation explains that this error component is only shown in production, and we will see a stack trace in our dev environment. To test our error component is working for SSR, leave the typo in the API call and create a new production build as we did before.
npm run build
npm run start
. Now you can visit your production build on localhost:3000
localhost:3000/characters
, you will see our error component, with additional information we didn't see in our client-side rendered component:
What is happening?
That's it! You now have personalized error components that show when your app encounters errors on both server-side and client-side.
Most APIs return status codes and descriptive error messages you can display inside your Error component if you want to. Play around with that - this code is just a template to get you started.
I hope that this was helpful! Have a great week everybody. ๐ค
30