Implementing a Loading Overlay with next/router Events in Next.js
Tags: nextjs, react, tailwindcss, ui
Published: 2023-08-02 15:33:26

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`:

```jsx
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:

```jsx
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! 🤣

[GitHub Repo](https://github.com/designly1/next-route-loader)

[Demo Site](https://next-route-loader.vercel.app/)

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](https://blog.designly.biz). Also, please leave your comments! I love to hear thoughts from my readers.

I use [Hostinger](https://hostinger.com?REFERRALCODE=1J11864) 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](https://designly.biz/contact).