Skip to content

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.

1 min read
  • react
  • next.js
  • snippets

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.events no longer exists — reach for nextjs-toploader or lean on loading.tsx + Suspense instead. 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/nprogress

2. 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.