How to build a progress bar indicator in Next.js
Tags: nextjs, webdev
Published: 2022-09-22 15:16:28

**Written by [David Omotayo](https://blog.logrocket.com/author/davidomotayo/)✏️**

Over the past several years, Next.js has further established its position as the [most popular backend framework built on React](https://2021.stateofjs.com/en-US/libraries/back-end-frameworks/). Several recent updates have either enhanced or added to this framework’s existing features. One example is its ability to render pages both on the server side and client side along with its integrated routing system, which makes navigation between these renderers seamless. 

However, transitioning between these renderers isn’t as buttery smooth as you might expect. Next.js takes a little bit of time to route between pages, especially to server-side rendered pages as these are rendered on request. A site may appear temporarily frozen each time a user tries to route to another page, and this can translate to a poor user experience. 

This tutorial will address this issue by demonstrating how to build and display a progress bar indicator when changing routes in a Next.js application. We’ll create one version of the loader component with custom CSS and we’ll also show how to use third-party libraries to add animation.

## Contents

*   [Prerequisites](#prerequisites)
*   [What are Next.js Router events?](#what-are-nextjs-router-events)
*   [Getting started](#getting-started)
*   [Creating the loader component](#creating-the-loader-component)
*   [Creating the Router events](#creating-the-router-events)
*   [Adding animation](#adding-animation)
    *   [Using NProgress](#using-nprogress)
    *   [Using React Spinners](#using-react-spinners)

## Prerequisites

To follow along with this tutorial, you‘ll need the following:

*   Fundamental knowledge of Next.js and React
*   Basic understanding of the [Next.js Routing system](https://nextjs.org/docs/routing/introduction)

## What are Next.js Router events?

Router events are event listeners that allow us to track route changes in Next.js via the `Router` object. The object provides properties that we can use to listen to different events happening in the Next.js Router and perform certain actions based on the events. 

For example, if we want to log `"route is changing"` to the console every time a user clicks on a link and navigates to another page, we can use the `"routeChangeStart"` **Router** event, like so:

```javascript
  Router.events.on("routeChangeStart", (url)=>{
    console.log(“route is changing”)
    })  
```

The `routeChangeStart` event fires when a route starts to change. It returns a callback function that we can use to run codes and actions. As you can see in the above code, the event fires a callback function that logs the `"route is changing"` string to the console. 

Here is a list of some supported Next.js Router events:

*   `routeChangeStart`: fires when a route starts to change
*   `routeChangeComplete`: fires when a route change is completed
*   `routeChangeError`: fires when an error occurs while changing routes, or when a route load is canceled
*   `beforeHistoryChange`: fires before changing the Router’s route history

Visit the [Next.js documentation](https://nextjs.org/docs/api-reference/next/router#routerevents) to learn more about these Router events.

## Getting started

I'll assume you already have a Next.js project set up. If not, you can fork the [sample project's CodeSandbox](https://codesandbox.io/s/gallant-browser-obriew?file=/styles/About.module.css) to follow along. 

The sample project used in this tutorial is a simple Next.js app based on the _Rick and Morty_ sitcom. The sample app has three pages: Home, About, and Characters. The first two pages are client-side rendered, while the Characters page is server-side rendered. ![Rick and Morty Site](https://blog.logrocket.com/wp-content/uploads/2022/09/rick-and-morty-site.png) 

Inside the Characters page, we're using the `getServerSideProps` function to fetch and display data from the [Rick and Morty API](https://rickandmortyapi.com):

```javascript
const Characters = ({ data }) => {
    return (  
        <div className={styles.container}>
        ...
        </div>
    );
}

export default Characters;

export async function getServerSideProps() {
    const delay = (s) => new Promise(resolve => setTimeout(resolve, s))
    const res = await fetch("https://rickandmortyapi.com/api/character")
    await delay(2000)
    const data = await res.json();

    return {
        props: {data}
    }
}
```

The Characters page is pre-generated on initial load, but the fetching request is only completed when a user clicks on the route. This way, the page’s content will only be displayed to the user when the request is finished, which may take some time. ![Characters Page Loading](https://blog.logrocket.com/wp-content/uploads/2022/09/characters-page-loading.gif) Instead of leaving the user clueless as to what's going on during those few seconds, we’ll display a progress bar indicator using the Next.js Router events. 

For this reason, we’ve added a 2s delay to the fetching process using the `delay` promise, as shown in the below code:

```javascript
const delay = (s) => new Promise(resolve => setTimeout(resolve, s))
 ...
await delay(2000)
 ...
```

This `delay` will give us time to showcase the progress indicator when we route to the Characters page. 

To finish setting up, run the following command in your terminal to install two third-party libraries, NProgress and React Spinners, that we'll be using later in this tutorial.

```javascript
npm i  --save nprogress react-spinners
```

These libraries are animated progress bar providers; we'll talk more about them later in this article.

## Creating the loader component

Next, we need to create a component that will wrap the progress indicator element. 

Start by creating a component folder and add a `Loader.js` file inside. ![Loader File](https://blog.logrocket.com/wp-content/uploads/2022/09/loader-file.png) 

Now, open the `loader.js` file and add the following code:

```javascript
const Loader = () => {
    return (
        <div className={styles.wrapper}>
            <div className={styles.loader}>

      </div>
        </div>
     );
}

export default Loader;
```

The code contains two `div` tags, a `wrappe`r div, and a nested `loader` div. If you plan on creating a custom progress bar indicator with CSS, these elements will come in handy. 

Next, navigate to the `styles` folder inside your project root folder, and create a new CSS module for the loader component. ![New Loader Module](https://blog.logrocket.com/wp-content/uploads/2022/09/new-loader-module.png) Inside the module, add the following code:

```javascript
.wrapper{
    width: 100%;
    height: 100vh;
    position: absolute;
    top: 0;
    left: 0;
    background-color: rgb(0, 0, 0, 0.3);
    backdrop-filter: blur(10px);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 99;
}

/*code for custom loading icon*/

.loader{
    border: 10px solid #f3f3f3;
    border-radius: 50%;
    border-top: 10px solid #505050;
    width: 60px;
    height: 60px;
    animation: spin 2s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}
```

The above code will make the `wrapper` div take up the entire viewport with a backdrop filter and will center every child element (in this case, the `loader` div) within the `wrapper`. 

**_N.B.,_** _the CSS properties within the_ `loader` _selector are for a custom CSS progress bar indicator; ignore this if you'd prefer to use NProgress or React Spinners library instead_ 

Now, go back to the `loader.js` component and import the CSS module at the top of the code block, like so:

```javascript
import styles from '../styles/Loader.module.css'
```

Next, we’ll move on to creating the Router events.

## Creating the Router events (`routeChangeStart`, `routeChangeComplete`, and `routeChangeError`)

Since we'll be displaying the progress bar indicator on every page route, rather than on a particular route, we're going to call the Router event listeners directly inside the `_app.js` component. 

If your project doesn't have an `_app.js` component, go to the `pages` folder, create a new `_app.js` file, and add the following code:

```javascript
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return ( <Component {...pageProps} /> )
}

export default MyApp
```

Next.js will use the `_app.js` component to initialize the pages in our project. We can override it and control the initialization. 

Next, import the `Router` object, `useState`, and `useEffect` Hooks inside the `_app.js` component, like so:

```javascript
import Router from 'next/router'
import { useState, useEffect } from 'react';
```

With the `Router` object imported, we can start declaring the events. 

We want to be able to track when a route starts to change, has changed, and when an error occurs either while changing routes or when a route load is canceled. Therefore, we'll subscribe to the `routeChangeStart`, `routeChangeComplete`, and `routeChangeError` events, respectively. 

First, create an `isLoading` state variable using the `useState` Hook we imported earlier and pass it a default Boolean value of `false`.

```javascript
  const [isLoading, setIsLoading] = useState(false);
```

Then, call the `useEffect` Hook and add the `routeChangeStart` event listener inside its callback function.

```javascript
useEffect(() => {
    Router.events.on("routeChangeStart", (url)=>{

    });

}, [Router])
```

Next, set the value of the `isLoading` state variable to `true` inside the event’s callback function, like so:

```javascript
Router.events.on("routeChangeStart", (url)=>{
    setIsLoading(true)
  });   
```

Now, create two more `Router` event listeners below the first, one for `routeChangeComplete` and one for `routeChangeError`:

```javascript Router.events.on("routeChangeComplete", (url)=>{
    setIsLoading(false)
  });

  Router.events.on("routeChangeError", (url) =>{
    setIsLoading(false)
  }); 
```

The `routeChangeComplete` and `routeChangeError` events will be responsible for ending the loading session, which was initiated by the `routeChangeStart` event, when the route has completely changed or when an error occurs. 

After completing the above steps, your `useEffect` function should look like this:

```javascript
useEffect(() => {
    Router.events.on("routeChangeStart", (url)=>{
      setIsLoading(true)
    });

    Router.events.on("routeChangeComplete", (url)=>{
      setIsLoading(false)
    });

    Router.events.on("routeChangeError", (url) =>{
      setIsLoading(false)
    });

  }, [Router])
```

Now we have a state that reacts to the different events happening inside the Next.js Router. 

Next, we’ll import the loader component we created earlier and render it based on the state of the `isLoading` variable.

```javascript
import Router from 'next/router'
import { useState, useEffect } from 'react';
import Loader from '../component/loader';

function MyApp({ Component, pageProps }) {

  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {

    ...

  }, [Router])

  return (
    <>
      {isLoading && <Loader/>}
      <Component {...pageProps} />
    </>
  )
}

export default MyApp
```

What we’re doing here is pretty self-explanatory. We import the loader component and conditionally render it to the view. 

If you use the custom CSS loader we created earlier, you should see a similar loading screen to that shown below when you try routing within the application. ![About Page](https://blog.logrocket.com/wp-content/uploads/2022/09/about-page.gif)

## Adding animation

The progress indicator shown in the above section was built using custom CSS. Now, let’s experiment with adding animation using two libraries: NProgress and React Spinners.

### Using NProgress

[NProgress](https://ricostacruz.com/nprogress/) is a lightweight library that lets us display realistic trickle animations at the top of the viewport to indicate loading progress, instead of using an animated loading icon. 

To use NProgress, import the `NProgress` function inside the `_app.js` component, like so:

```javascript
import NProgress from 'nprogress'
```

The function has a set of methods we can use to display and configure the progress bar animation. Here are some of the available methods:

*   `start`: shows the progress bar
*   `set`: sets a percentage
*   `inc`: increments by a little
*   `done`: completes the progress
*   `configure`: configures preference

See the [NProgress official documentation](https://ricostacruz.com/nprogress/) to learn more about these methods. 

Next, call the function's `start()` and `done()` methods inside the `routeChangeStart` and `routeChangeComplete` event callbacks, respectively:

```javascript
Router.events.on("routeChangeStart", (url)=>{
    Nprogress.start()
  })

Router.events.on("routeChangeComplete", (url)=>{
      Nprogress.done(false)
    });
```

Finally, we'll add the NProgress-associated CSS to our project via the below CDN link:

```javascript
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.css" integrity="sha512-42kB9yDlYiCEfx2xVwq0q7hT4uf26FUgSIZBK8uiaEnTdShXjwr8Ip1V4xGJMg3mHkUt9nNuTDxunHF0/EgxLQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />  
```

To go about this, import the `Head` component from Next.js and nest the CDN link within the declaration, like so:

```javascript
  ...
import Head from "next/head"

function MyApp({ Component, pageProps }) {

  ...

  return (
    <>
      <Head>
        <link rel="stylesheet" ... />
      </Head>
      <Component {...pageProps} />
    </>
  )
}
```

If you save your progress and click on a route in the browser, you should see a progress bar and a spinning icon at the top of the viewport similar to that shown below. ![Progress Bar](https://blog.logrocket.com/wp-content/uploads/2022/09/progress-bar.gif) To disable the spinning icon at the top-right corner of the viewport, call the `configure` method and pass it an object with a `showSpinner` property and the value set to `false`.

```javascript
Nprogress.configure({showSpinner: false});
```

Now if you save the code and go back to the browser, the spinning icon should be gone. ![No Spinning Icon](https://blog.logrocket.com/wp-content/uploads/2022/09/no-spinning-icon.gif)

### Using React Spinners

[React Spinners](https://www.davidhu.io/react-spinners/) is a lightweight library comprised of a collection of React-based progress indicators. The library provides varieties of animated loading icons that can be used to indicate loading progress. ![Various Loading Icons](https://blog.logrocket.com/wp-content/uploads/2022/09/various-loading-icons.gif) 

The progress indicators are exposed from the library as components. These components take in props that we can use to customize their speed, size, loading state, and color. 

To use React Spinners, go back to the `loader.js` component and import a spinner component, like so:

```javascript
import {HashLoader} from 'react-spinners'
```

You can choose and configure your preferred spinner from the [product page](https://www.davidhu.io/react-spinners/). 

Next, nest the spinner component inside the `wrapper` div and save your progress.

```javascript
<div className={styles.wrapper}>
      <HashLoader
       color="#eeeeee"
       size={80}
       />
</div>
```

Now, if you go back to the browser, you should see a nicely animated loading icon when you route between pages. ![Fancy Loading Icon](https://blog.logrocket.com/wp-content/uploads/2022/09/fancy-loading-icon.gif)

## Conclusion

In this article, we introduced Next.js Router events and demonstrated how to use them to track routing activities and display a progress bar indicator. 

We demonstrated how to create a loader component that renders a progress bar indicator based on the events emitted by the Router and also added customized loaders using the NProgress and React Spinners libraries.

---

## [LogRocket](https://logrocket.com/signup): Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try [LogRocket](https://logrocket.com/signup). 

[![LogRocket signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://logrocket.com/signup)

[LogRocket](https://logrocket.com/signup) is like a DVR for web and mobile apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — [start monitoring for free](https://logrocket.com/signup).