Route transitions significantly impact the user experience of your web application. Smooth, informative transitions keep your users engaged and informed. Today, we'll explore a practical aspect of improving this UX in Next.js applications: building a route change loading overlay using next/router
events.
Keep in mind, this tutorial uses the next/router
package, not the newer next/navigation
. That being said, this will only work with the old (still in heavy use) pages router, not the new app router. A tutorial on loaders/routing events for the app router is forthcoming.
There is also a GitHub repo and demo companion site for this tutorial. Links to both are that the bottom of this article. So, without further ado, let's get coding!
Setting Up Environment
The quickest way to get up and running would be to clone my repo. But if you want to start from scratch, just spin up a new Next.js project using create-next-app@latest
with the following settings:
Typescript? Yes
ESLint? Yes
Tailwind CSS? Yes
src/ directory? Yes
App Router? No
import alias? Yes
leave default import alias
This will install all our dependencies. You shouldn't need to install anything else.
Our Loader Component
Now create a folder src/components
and in it, a file called RouteLoader.tsx
:
import React, { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
const Loader = () => (
<div className="fixed top-0 left-0 w-screen h-screen z-[99999999999999] flex items-center justify-center bg-black/40">
<div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-white"></div>
</div>
);
export default function RouteLoader() {
const router = useRouter();
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
const handleStart = (url: string) => setLoading(true);
const handleComplete = (url: string) => setLoading(false);
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);
return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleComplete);
};
}, [router]);
return loading ? <Loader /> : null;
}
Should be pretty self-explanatory here. We bind the routeChangeStart
event to handleStart
, and routeChangeComplete
and routeChangeError
to handleComplete
. We also want to remove these listeners when the component is unmounted, so we return a function that unbinds the events. Basically, what will happen is as soon as a route change is detected, loading
state will be set to true. This will show our full-screen loading overlay. Once the page is done loading, the handleComplete
event will fire (on success or error) and loading
will be set to false.
Finally, in our _app.tsx
file, we'll modify it to import our loader component:
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import RouteLoader from '@/components/RouteLoader'
import Header from '@/components/Header'
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function App({ Component, pageProps }: AppProps) {
const classes = [inter.className, 'min-h-screen'];
return <main className={classes.join(' ')}>
<RouteLoader />
<Header />
<div className="flex flex-col">
<Component {...pageProps} />
</div>
</main>
}
That's it! This is a very basic example. You could create a much fancier loader using an animated SVG or GIF, but it's generally good practice to make your loader very lightweight. Otherwise you'd need a loader for your loader! 🤣
Thank you for taking the time to read my article and I hope you found it useful (or at the very least, mildly entertaining). For more great information about web dev, systems administration and cloud computing, please read the Designly Blog. Also, please leave your comments! I love to hear thoughts from my readers.
I use Hostinger to host my clients' websites. You can get a business account that can host 100 websites at a price of $3.99/mo, which you can lock in for up to 48 months! It's the best deal in town. Services include PHP hosting (with extensions), MySQL, Wordpress and Email services.
Looking for a web developer? I'm available for hire! To inquire, please fill out a contact form.