Custom Loading and 404 Pages in Next.js 13 Tutorial
August 7, 2023
Welcome to the tutorial on creating a custom loading component and 404 error page using the latest features of Next.js 13. In this guide, we’ll explore the use of the ‘loading.tsx’ and ‘not-found.tsx’ files to enhance user experience.
Setting Up the Environment
Before we dive into creating custom loading and error pages, let’s set up our working environment. Follow these steps to get started:
- Create a new Next.js project. You can type 'npx create-next-app@latest' in the terminal, write the name of your project and follow preselected options. More about it you can find in the documentation.
- Run 'npm run dev' to start the development server.
- Clean up the default 'page.tsx' and globals.css files.
Here’s an example of the cleaned 'page.tsx' file:
export default function Home() {
return (
<>
<h1>Homepage</h1>
</>
);
}
Creating a Global Loader
To start, let’s create a loader that will be displayed on all pages. Follow these steps:
- Create an 'about' folder within the 'app' folder and add a 'page.tsx' file inside.
- In the 'layout.tsx' file, add links to the home and about pages. Apply a 'nav-bar' class for styling purposes (in 'globals.css' add a 'display: flex' and 'gap' styling to '.nav-bar' class)
Here’s how your 'layout.tsx' might look:
import "./globals.css";
import type {Metadata} from "next";
import {Inter} from "next/font/google";
import Link from "next/link";
const inter = Inter({subsets: ["latin"]});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<div className="nav-bar">
<h3>
<Link href="/">Home</Link>
</h3>
<h3>
<Link href="/about">About</Link>
</h3>
</div>
{children}
</body>
</html>
);
}
'app/about/page.tsx' will look like this:
const About = () => {
return (
<>
<h1>About</h1>
</>
);
};
export default About;
- Now, let’s create a ‘loading.tsx’ file in the ‘app’ folder to implement a global loading indicator. We’ll apply the ‘global’ className to distinguish it as a globally used component and style it with a red background color in ‘globals.css’.
export default function Loading() {
return <h2 className="global">Loading...</h2>;
}
The result will be like this:
Implementing Page-Specific Loaders
To implement a loader specific to a certain page, create another ‘loading.tsx’ file within the respective page folder. For example, let’s create a ‘contact’ folder mirroring the ‘about’ structure but with a unique loader. Copy the existing ‘loading.tsx’ file and modify the className to be ‘unique’. In ‘globals.css’, apply a green background color to this unique loader.
Now, the contact page’s loading indicator will be green:
This flexibility extends to child pages as well. If we add a ‘child’ folder with ‘page.tsx’ inside the ‘contact’ folder, the child page will inherit its closest parent’s loader.
‘app/contact/page.tsx’
import Link from "next/link";
const Contact = () => {
return (
<>
<h1>Contact</h1>
<h3>
<Link href="contact/child">Child </Link>
</h3>
</>
);
};
export default Contact;
Here we see that ‘child’ page uses its closest parent’s ‘loading.tsx’ — that’s why it is green as well:
This dynamic loading customization is a powerful feature of Next.js, allowing you to enhance the user experience by applying custom loading text or ‘skeleton’ components to ensure smoother page transitions.
Implementing a Global 404 Page
Let’s implement a custom 404 error page that applies globally.
To achieve this, create a ‘not-found.tsx’ file in the ‘app’ folder. Just like the previous example, we’ll use the ‘global’ className and style it with red color to indicate its global usage.
‘app/not-found.tsx’
import Link from "next/link";
export default function NotFound() {
return (
<div className="global">
<h2>Not Found</h2>
<p>Could not find requested resource</p>
<Link href="/">Return Home</Link>
</div>
);
}
Additionally, let’s add a link to a non-existing route in the ‘layout.tsx’:
<Link href="/not-existing-route">Not Found</Link>
Now, our global not-found page will be displayed when routes are not found. This global 404 page also applies to child routes. For instance, accessing the ‘/about/test’ URL will trigger the red-colored not-found page.
Handling Dynamic Routes
Next, let’s explore dynamic routes. Create a ‘user’ folder with a ‘page.tsx’ file inside it. A Dynamic Segment can be created by wrapping a folder’s name in square brackets: [folderName]. Within the ‘user’ folder, add another folder named ‘[id]’. For instance, you can access dynamic user profiles like ‘/user/1’, ‘/user/2’, etc.
To validate the user ID, we’ll implement a rule that restricts the ID to a range between 0 and 10. If an invalid ID is provided, we’ll use the ‘notFound’ function from “next/navigation” to throw a ‘NEXT_NOT_FOUND’ error.
‘user/[id]/page.tsx’
import {notFound} from "next/navigation";
const Id = ({params: {id}}: { params: { id: number } }) => {
id = Number(id);
if (id < 0 || id > 10 || isNaN(id)) {
notFound();
}
return (
<>
<h1>Id:{id}</h1>
</>
);
};
export default Id;
In the ‘layout.tsx’, we’ll add a link to the ‘/user’ route.
The result will look like this:
Unique Not-Found Page for Dynamic Routes
If you wish to provide a unique ‘not-found’ page for incorrect dynamic route IDs, simply add a ‘not-found.tsx’ file within the ‘[id]’ folder. You can modify the className to ‘unique’, which will now render the content in green.
Thank you for following along!
You can find the source code for this project on GitHub.