How to integrate NProgress with Next.js
Snippets for showing a top loading bar while Next.js navigates between pages, using NProgress wired to the Pages Router events.
This article was written in 2021 for Next.js with the Pages Router. If your project is on the App Router (Next.js 13+),
router.eventsno longer exists — reach fornextjs-toploaderor lean onloading.tsx+Suspenseinstead. The snippets below still apply to legacy Pages Router projects.
If you've used Next.js, you've probably noticed that navigation between pages isn't always instant — the router needs a moment to fetch the destination page's data. A small top loading bar is a low-cost way to tell the user something is happening.
Here's how to integrate NProgress with Next.js (Pages Router).
1. Install NProgress
pnpm add nprogress
pnpm add -D @types/nprogress2. Wire it into the router events in _app.tsx
import * as React from 'react';
import type { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import NProgress from 'nprogress';
function MyApp({ Component, pageProps }: AppProps) {
const router = useRouter();
React.useEffect(() => {
const handleStart = () => NProgress.start();
const handleStop = () => NProgress.done();
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleStop);
router.events.on('routeChangeError', handleStop);
return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleStop);
router.events.off('routeChangeError', handleStop);
};
}, [router]);
return <Component {...pageProps} />;
}
export default MyApp;3. Style the bar and spinner
/* globals.css */
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: rgba(167, 139, 250, 0.8);
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 4px;
}
/* glow at the leading edge */
#nprogress .peg {
display: block;
position: absolute;
right: 0;
width: 100px;
height: 100%;
box-shadow:
0 0 10px rgba(167, 139, 250, 0.8),
0 0 5px rgba(167, 139, 250, 0.8);
opacity: 1;
transform: rotate(3deg) translate(0, -4px);
}
/* Drop this block if you don't want the spinner */
#nprogress .spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
#nprogress .spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: rgba(167, 139, 250, 0.8);
border-left-color: rgba(167, 139, 250, 0.8);
border-radius: 50%;
animation: nprogress-spinner 400ms linear infinite;
}
@keyframes nprogress-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}That's it — a small purple bar will show up at the top whenever the router transitions. Tiny detail, big UX win.
Thanks for reading.