Add NProgress navigation progress bar (#139)

Installs nprogress and wires it to React Router's useNavigation() hook
so a progress bar appears at the top of the page during route transitions.
Themed to the site's --electric CSS variable with the spinner disabled.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-03-11 22:02:48 -07:00 committed by GitHub
parent 3dc6549640
commit f97ee4e4d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 47 additions and 0 deletions

View file

@ -99,3 +99,12 @@ html {
@apply bg-background text-foreground;
}
}
/* NProgress theme override */
#nprogress .bar {
background: var(--electric) !important;
height: 3px !important;
}
#nprogress .peg {
box-shadow: 0 0 10px var(--electric), 0 0 5px var(--electric) !important;
}

View file

@ -0,0 +1,20 @@
import { useNavigation } from "react-router";
import { useEffect } from "react";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
NProgress.configure({ showSpinner: false });
export function NavigationProgress() {
const { state } = useNavigation();
useEffect(() => {
if (state !== "idle") {
NProgress.start();
} else {
NProgress.done();
}
}, [state]);
return null;
}

View file

@ -12,6 +12,7 @@ import type { Route } from "./+types/root";
import "./app.css";
import { clerkMiddleware, rootAuthLoader, getAuth } from "@clerk/react-router/server";
import { Navbar } from "~/components/navbar";
import { NavigationProgress } from "~/components/NavigationProgress";
import { ClerkProvider } from "@clerk/react-router";
import { dark } from "@clerk/themes";
import { Toaster } from "~/components/ui/sonner";
@ -75,6 +76,7 @@ export default function App({ loaderData }: Route.ComponentProps) {
return (
<ClerkProvider loaderData={loaderData} appearance={{ baseTheme: dark }}>
<NavigationProgress />
{!isDraftRoute && <Navbar isAdmin={loaderData?.isAdmin ?? false} />}
{isDraftRoute ? (
<Outlet />

14
package-lock.json generated
View file

@ -28,6 +28,7 @@
"@react-router/express": "^7.7.1",
"@react-router/node": "^7.7.1",
"@sentry/react-router": "^10.43.0",
"@types/nprogress": "^0.2.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"compression": "^1.8.0",
@ -39,6 +40,7 @@
"lucide-react": "^0.545.0",
"morgan": "^1.10.0",
"next-themes": "^0.4.6",
"nprogress": "^0.2.0",
"postgres": "^3.4.5",
"react": "^19.1.0",
"react-day-picker": "^9.11.1",
@ -6102,6 +6104,12 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/nprogress": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@types/nprogress/-/nprogress-0.2.3.tgz",
"integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==",
"license": "MIT"
},
"node_modules/@types/pg": {
"version": "8.15.6",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz",
@ -12157,6 +12165,12 @@
"node": ">=8"
}
},
"node_modules/nprogress": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
"integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==",
"license": "MIT"
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",

View file

@ -43,6 +43,7 @@
"@react-router/express": "^7.7.1",
"@react-router/node": "^7.7.1",
"@sentry/react-router": "^10.43.0",
"@types/nprogress": "^0.2.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"compression": "^1.8.0",
@ -54,6 +55,7 @@
"lucide-react": "^0.545.0",
"morgan": "^1.10.0",
"next-themes": "^0.4.6",
"nprogress": "^0.2.0",
"postgres": "^3.4.5",
"react": "^19.1.0",
"react-day-picker": "^9.11.1",