DEV Community

Vincent Voyer
Vincent Voyer

Posted on

48 11

Use NProgress with Next.js (Router and fetch events)

Today I was trying to add NProgress https://github.com/rstacruz/nprogress to my Next.js project.

I wanted the progress bar to:

  1. show when switching routes / pages
  2. show when any fetch call is made
  3. display only after a delay, I don't want to show a loader at EVERY interaction, only when the requests are "slow"

Here's a demo of how NProgress looks like:
Demo of NProgress

Since I hit met some challenges while implementing all of that, I felt like it would be good to share how I did it. So here it is:

First, install the nprogress package:



npm install nprogress


Enter fullscreen mode Exit fullscreen mode

Then edit or create your _app.js and add:



// global styles are required to be added to `_app.js` per Next.js requirements.
import "nprogress/nprogress.css";

const TopProgressBar = dynamic(
  () => {
    return import("components/TopProgressBar");
  },
  { ssr: false },
);

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


Enter fullscreen mode Exit fullscreen mode

Here we use dynamic imports and the ssr option to make sure our TopProgressBar is loaded only in browser environements.

If you're wondering how relatively loading components/TopProgressBar works, just configure you jsconfig.json as shown in the Next.js documentation.

Finally create components/TopProgressBar.js:



import Router from "next/router";
import NProgress from "nprogress";

let timer;
let state;
let activeRequests = 0;
const delay = 250;

function load() {
  if (state === "loading") {
    return;
  }

  state = "loading";

  timer = setTimeout(function () {
    NProgress.start();
  }, delay); // only show progress bar if it takes longer than the delay
}

function stop() {
  if (activeRequests > 0) {
    return;
  }

  state = "stop";

  clearTimeout(timer);
  NProgress.done();
}

Router.events.on("routeChangeStart", load);
Router.events.on("routeChangeComplete", stop);
Router.events.on("routeChangeError", stop);

const originalFetch = window.fetch;
window.fetch = async function (...args) {
  if (activeRequests === 0) {
    load();
  }

  activeRequests++;

  try {
    const response = await originalFetch(...args);
    return response;
  } catch (error) {
    return Promise.reject(error);
  } finally {
    activeRequests -= 1;
    if (activeRequests === 0) {
      stop();
    }
  }
};

export default function () {
  return null;
}


Enter fullscreen mode Exit fullscreen mode

Here we register to Next.js router events and monkey patch the global fetch. I was worried monkey patching fetch would fail to register early on but turns out it works 🤷‍♂️!

As you can see, TopProgressBar renders nothing, I guess there might be issues of doing things this way so if you encounter some, just let me know!

That's it!

If you're wondering if NProgress is still maintained because of the low number of commits and "high" number of issues, the good news is that they are working on a new version for 2020: https://github.com/rstacruz/nprogress/pull/218

Even if you're not using Next.js, you should be able to adapt this code to your favorite platform or framework.

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (8)

Collapse
 
mxmzb profile image
Maxim •

Nice, writeup. I instantly was reminded of doing this with Turbolinks in Rails back in the days.

Although it's technically a nice solution, I actually dislike the top progress bar on websites. Somehow, I always feel like I have to wait longer because of the progress bar. Maybe it's because only slow loading sites use it, and I am wrongly attributing it to the bar. 🤷‍♂️

Collapse
 
vvo profile image
Vincent Voyer •

Yep indeed it can feel this way, when it's fast enough though and delayed then it's a good indicator that "the UI is up to date now"

Collapse
 
supermin profile image
Anturin Durimov • • Edited

Hi, I like the solution. But if you use SWR with fetch, then with each revalidation the progress bar will appear. Is there a way to turn it off for SWR revalidate fetches?

Collapse
 
vvo profile image
Vincent Voyer • • Edited

Good question; I am experiencing the same as you because of using SWR. For now, I am fine with it, but you're right; there should be a way to disable it.

Ways to do this:

  • use a custom fetcher that calls window. fetch() with some specific (non-standard) params allowing you to identify the calls to fetch are from SWR
  • in your fetch monkey patch, find the URL path, and if it's "/api" then you could not show the progress

If you try one of those or find other ways, let me know!

Collapse
 
antchinchilla profile image
Anthony Ch •

Hi, it works good with routing but it doesnt work with fetch, im using node-fetch, is this the issue? which fetch package are you using for this? thanks!

Collapse
 
vvo profile image
Vincent Voyer •

Hi there, with the latest Next.js versions you do not even have to include any fetch polyfill, so you can remove any fetch package and it should just works

Collapse
 
titungdup profile image
dhondup •

Hi, Does NProgress also work for initial site load? Like a loader?

Collapse
 
titungdup profile image
dhondup •

Also, for some reason, the code is not working after adding TopProgressBar in _app.js

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up