Route Progress bar in Next js
Implementing a Route Progress Bar in a Next.js and Tailwind CSS App
HEY 👋, Welcome to this tutorial on adding a route progress bar to a Next.js and Tailwind CSS app! A route progress bar is a visual indicator that displays the progress of a user's journey as they navigate through an app or website. This can be a useful tool for enhancing the user experience and providing feedback to users on their progress.
In this tutorial, we will be using Next.js, a popular framework for building server-rendered React applications, and Tailwind CSS, a utility-first CSS framework. We will walk through the steps of implementing a route progress bar in a Next.js app and styling it with Tailwind CSS.
Whether you are new to Next.js and Tailwind CSS or have some experience with these technologies, this tutorial will provide you with the knowledge and skills you need to add a route progress bar to your own projects. Let's get started!
Next js and tailwind CSS setup
I am using Tailwind css as a flavour of css to style my Next js to setup Next js with Tailwind css you can use this template
or use this officail guid from tailwind css docs
NProgress
to help with the loading bar logic you'll be using a package called NProgress because it makes it easier to style components with Tailwind CSS class names.
to install run yarn add react-nprogress
Bar component
Now that we have setup out of the way we can start working on our actual componets here
so create a Componets
directory and create a file bar.tsx
and add this code into that bar.tsx
file
interface Props {
animationDuration: number;
progress: number;
}
export const Bar = ({ animationDuration, progress }: Props) => (
<div
className="bg-brand fixed left-0 top-0 z-50 h-1 w-full"
style={{
marginLeft: `${(-1 + progress) * 100}%`,
transition: `margin-left ${animationDuration}ms linear`,
}}
></div>
);
interface Props {
animationDuration: number;
progress: number;
}
export const Bar = ({ animationDuration, progress }: Props) => (
<div
className="bg-brand fixed left-0 top-0 z-50 h-1 w-full"
style={{
marginLeft: `${(-1 + progress) * 100}%`,
transition: `margin-left ${animationDuration}ms linear`,
}}
></div>
);
Props values of animationDuration
and Progress
is coming from react-nprogress
Container component
create a new file container.tsx
and paste this code in the file ..
import { ReactNode } from "react";
interface Props {
animationDuration: number;
children: ReactNode;
isFinished: boolean;
}
export const Container = ({
animationDuration,
children,
isFinished,
}: Props) => (
<div
className="pointer-events-none"
style={{
opacity: isFinished ? 0 : 1,
transition: `opacity ${animationDuration}ms linear`,
}}
>
{children}
</div>
);
import { ReactNode } from "react";
interface Props {
animationDuration: number;
children: ReactNode;
isFinished: boolean;
}
export const Container = ({
animationDuration,
children,
isFinished,
}: Props) => (
<div
className="pointer-events-none"
style={{
opacity: isFinished ? 0 : 1,
transition: `opacity ${animationDuration}ms linear`,
}}
>
{children}
</div>
);
this is the component that wrap the bar
componet and handle the logic if the loading is finsihed or not using isFinished
prop that allow to change opacity of the bar .
Progress Bar component
Now that we have the bar
and container
component out of the way let's finish the Progress Component create a file index.tsx
add this code .
import { useNProgress } from "@tanem/react-nprogress";
import { Bar } from "./bar";
import { Container } from "./progressContainer";
interface Props {
isAnimating: boolean;
}
const Progress = ({ isAnimating }: Props) => {
const { animationDuration, isFinished, progress } = useNProgress({
isAnimating,
});
return (
<Container animationDuration={animationDuration} isFinished={isFinished}>
<Bar animationDuration={animationDuration} progress={progress} />
</Container>
);
};
export default Progress;
import { useNProgress } from "@tanem/react-nprogress";
import { Bar } from "./bar";
import { Container } from "./progressContainer";
interface Props {
isAnimating: boolean;
}
const Progress = ({ isAnimating }: Props) => {
const { animationDuration, isFinished, progress } = useNProgress({
isAnimating,
});
return (
<Container animationDuration={animationDuration} isFinished={isFinished}>
<Bar animationDuration={animationDuration} progress={progress} />
</Container>
);
};
export default Progress;
This component uses useNProgress
hook from @tanem/react-nprogress
to get the animationDuration
, isFinished
, progress
.
this takes a isAnimating
Prop and return all the value and can be used by Bar
and Container
Componenet.
Add Progress Bar
Now let's import the Progress Bar component use in _app.tsx
file.
import { useState, useEffect } from "react";
import "tailwindcss/tailwind.css";
import { useRouter } from "next/router";
import { Progress } from "/components";
function MyApp({ Component, pageProps }) {
const [isAnimating, setIsAnimating] = useState(false);
const router = useRouter();
useEffect(() => {
const handleStart = () => {
setIsAnimating(true);
};
const handleStop = () => {
setIsAnimating(false);
};
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 (
<>
<Progress isAnimating={isAnimating} />
<Component {...pageProps} />
</>
);
}
export default MyApp;
import { useState, useEffect } from "react";
import "tailwindcss/tailwind.css";
import { useRouter } from "next/router";
import { Progress } from "/components";
function MyApp({ Component, pageProps }) {
const [isAnimating, setIsAnimating] = useState(false);
const router = useRouter();
useEffect(() => {
const handleStart = () => {
setIsAnimating(true);
};
const handleStop = () => {
setIsAnimating(false);
};
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 (
<>
<Progress isAnimating={isAnimating} />
<Component {...pageProps} />
</>
);
}
export default MyApp;
here we are createing a state isAnimating
and passing that to Progress
Component.
and creating function to set that state and run these function bassed on the router events
when changing a route router
emits events that in useEffect listining and using that to set the state of the progress bar .
in the cleanup function we are reseting the events to pervent some bad behaviour and memory leasks.
Conclusion
n this tutorial, we learned how to create a simple progress bar with NProgress and how to listen to router events using the useRouter and useEffect hooks.
see you in a next one.