Today I was trying to add NProgress https://github.com/rstacruz/nprogress to my Next.js project.
I wanted the progress bar to:
- show when switching routes / pages
- show when any fetch call is made
- 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:
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
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} />
</>
}
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;
}
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.
Top comments (8)
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. π€·ββοΈ
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"
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?
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:
If you try one of those or find other ways, let me know!
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!
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
Hi, Does NProgress also work for initial site load? Like a loader?
Also, for some reason, the code is not working after adding TopProgressBar in _app.js