DEV Community

Cover image for Implementing a Loading Overlay with next/router Events in Next.js
Jay @ Designly
Jay @ Designly

Posted on • Originally published at blog.designly.biz

1

Implementing a Loading Overlay with next/router Events in Next.js

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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

Demo Site

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.

Playwright CLI Flags Tutorial

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • --last-failed: Zero in on just the tests that failed in your previous run
  • --only-changed: Test only the spec files you've modified in git
  • --repeat-each: Run tests multiple times to catch flaky behavior before it reaches production
  • --forbid-only: Prevent accidental test.only commits from breaking your CI pipeline
  • --ui --headed --workers 1: Debug visually with browser windows and sequential test execution

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Practical examples included!

Watch Video 📹️

Top comments (2)

Collapse
 
tangducvi profile image
tangducvi

Nice tutorial

Collapse
 
designly profile image
Jay @ Designly

Thank you!

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • 0:56 --last-failed
  • 2:34 --only-changed
  • 4:27 --repeat-each
  • 5:15 --forbid-only
  • 5:51 --ui --headed --workers 1

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Click on any timestamp above to jump directly to that section in the tutorial!

Build With Me: AI-Powered Adaptive Web Scraper with LLMs

Join us for a hands-on session with Zia Ahmad where we build an AI-driven web scraper that adapts to site changes in real-time. Code along and level up your automation skills.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️