apal21 / nextjs-progressbar

A simple Next.js progressbar component using NProgress.

Home Page:https://www.npmjs.com/package/nextjs-progressbar

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Next 13 app dir support

Fedeorlandau opened this issue · comments

Hello there, I've noticed that next 13 removed router.events (the API that this lib uses to detect the route change)

There is a conversation going on here relevant for this topic: vercel/next.js#41745 (comment)

Anyone found a nasty workaround to get a progress bar at the top of the page on Next 13 (app directory)?

Thanks!

In chromium based browsers you might use the new native navigation api, though this doesn't work in Firefox or Safari 🤔.

useEffect(() => {
  const { navigation } = window;
  if (navigation) {
    navigation.addEventListener("navigate", routeChangeStart);
    navigation.addEventListener("navigateerror", routeChangeError);
    navigation.addEventListener("navigatesuccess", routeChangeEnd);
    return () => {
      navigation.removeEventListener("navigate", routeChangeStart);
      navigation.removeEventListener("navigateerror", routeChangeError);
      navigation.removeEventListener("navigatesuccess", routeChangeEnd);
    };
  }
}, []);

https://developer.chrome.com/docs/web-platform/navigation-api

Would love to see this addressed, more and more people are on Next 13 by the day

commented

Any updates on next13? is there any way to pull this off?

commented

!bump!
On production in app directory. Plz help:p

commented

Working with this module and it has an issue with the latest version of nextjs, any assistance will be much appreciated at this point.

commented

I couldn't find any way to implement it for appDir, you can still use it with pages dir in the latest version of nextjs but for appDir, since it's still in beta so for now use loading.tsx and if they bring back router.events or any similar api in appDir before the stable release, we'll add support for it

This discussion thread may be relevant:

@jmcmullen's workaround over there (seems kind of hacky, but maybe it's a way forward in the interim until the events are exposed by Next.js in the app directory?):

I managed to get it working on all links. You can create the following client component and include it inside your root layout.tsx file

"use client";
import { useEffect } from "react";
import NProgress from "nprogress";

type PushStateInput = [
  data: any,
  unused: string,
  url?: string | URL | null | undefined
];

export default function ProgressBar() {
  const height = "2px";
  const color = "#F6F01E";

  const styles = (
    <style>
      {`
        #nprogress {
          pointer-events: none;
        }
        #nprogress .bar {
          background: ${color};
          position: fixed;
          z-index: 99999;
          top: 0;
          left: 0;
          width: 100%;
          height: ${typeof height === `string` ? height : `${height}px`};
        }
        /* Fancy blur effect */
        #nprogress .peg {
          display: block;
          position: absolute;
          right: 0px;
          width: 100px;
          height: 100%;
          box-shadow: 0 0 10px ${color}, 0 0 5px ${color};
          opacity: 1.0;
          -webkit-transform: rotate(3deg) translate(0px, -4px);
              -ms-transform: rotate(3deg) translate(0px, -4px);
                  transform: rotate(3deg) translate(0px, -4px);
        }
    `}
    </style>
  );

  useEffect(() => {
    NProgress.configure({ showSpinner: false });

    const handleAnchorClick = (event: MouseEvent) => {
      const targetUrl = (event.currentTarget as HTMLAnchorElement).href;
      const currentUrl = location.href;
      if (targetUrl !== currentUrl) {
        NProgress.start();
      }
    };

    const handleMutation: MutationCallback = () => {
      const anchorElements = document.querySelectorAll("a");
      anchorElements.forEach((anchor) =>
        anchor.addEventListener("click", handleAnchorClick)
      );
    };

    const mutationObserver = new MutationObserver(handleMutation);
    mutationObserver.observe(document, { childList: true, subtree: true });

    window.history.pushState = new Proxy(window.history.pushState, {
      apply: (target, thisArg, argArray: PushStateInput) => {
        NProgress.done();
        return target.apply(thisArg, argArray);
      },
    });
  });

  return styles;
}

Any updates on next13? is there any way to pull this off?

I found this npm package: https://www.npmjs.com/package/nextjs-toploader

  • It perfectly works with the next js 13 in the layout.js for the app directory
commented

Any updates on next13? is there any way to pull this off?

I found this npm package: https://www.npmjs.com/package/nextjs-toploader

  • It perfectly works with the next js 13 in the page.js for the app directory

No shit

commented

Any updates on next13? is there any way to pull this off?

I found this npm package: https://www.npmjs.com/package/nextjs-toploader

* It perfectly works with the next js 13 in the `page.js` for the `app` directory

Any updates on next13? is there any way to pull this off?

I found this npm package: https://www.npmjs.com/package/nextjs-toploader

  • It perfectly works with the next js 13 in the page.js for the app directory

No shit

does it work?

Yeah, works for me very well.

commented

Yeah, works for me very well.

i put it in my _layout, it doesnt work

Any updates on next13? is there any way to pull this off?

I found this npm package: https://www.npmjs.com/package/nextjs-toploader

* It perfectly works with the next js 13 in the `page.js` for the `app` directory

Any updates on next13? is there any way to pull this off?

I found this npm package: https://www.npmjs.com/package/nextjs-toploader

  • It perfectly works with the next js 13 in the page.js for the app directory

No shit

does it work?

I inserted in my layout.tsx inside the body tag, it worked for me

Hey everyone, this is working for me in NextJS v13.

I have created a quick demo of this package.

Here is my package.json file:

{
  "name": "test-progress",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@types/node": "18.15.3",
    "@types/react": "18.0.28",
    "@types/react-dom": "18.0.11",
    "eslint": "8.36.0",
    "eslint-config-next": "13.2.4",
    "next": "13.2.4",
    "nextjs-progressbar": "^0.0.16",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "typescript": "5.0.2"
  }
}

This is pages/_app.tsx:

import type { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import NextProgressbar from 'nextjs-progressbar';
import { useEffect, useState } from 'react';

export default function App({ Component, pageProps }: AppProps) {
  const [isLoading, setLoading] = useState(false);

  const router = useRouter();

  useEffect(() => {
    // Handle route change
    router.events.on('routeChangeStart', (url) => {
      setLoading(true);
    });

    router.events.on('routeChangeError', () => {
      setTimeout(() => {
        setLoading(false);
      }, 2000);
    });

    router.events.on('routeChangeComplete', () => {
      setTimeout(() => {
        setLoading(false);
      }, 2000);
    });
  }, []);

  return (
    <>
      {isLoading ? <NextProgressbar /> : null}
      <Component {...pageProps} />
    </>
  );
}

This is pages/index.tsx:

import Link from 'next/link'

export default function Home() {
  return (
    <>
      Index
      <Link href="/about">About</Link>
    </>
  )
}

This is pages/about.tsx:

import Link from 'next/link'

function AboutPage() {
  return <h1>About Page <Link href="/">Home</Link></h1>
}

export default AboutPage

Keeping this thread open for a few more days.

@apal21 this is about the app/ directory, as noted in this paragraph in the description:

Anyone found a nasty workaround to get a progress bar at the top of the page on Next 13 (app directory)?

Probably the issue title should be changed.

Any updates on next13? is there any way to pull this off?

I found this npm package: https://www.npmjs.com/package/nextjs-toploader

  • It perfectly works with the next js 13 in the layout.js for the app directory

This seems a bit buggy. For me, it triggers the progress bar when I open a dropdown menu.

You can use nextjs-toploader lib which supports both /app and /pages directory. Until this starts supporting it.

import NextTopLoader from 'nextjs-toploader';


// for /app directory, put it in /layout.tsx file like this
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <NextTopLoader />
        {children}
      </body>
    </html>
  );
}



// for /pages directory, put it in /_app.tsx file like this
export default function MyApp({ Component, pageProps }) {
  return (
    <>
      <NextTopLoader />
      <Component {...pageProps} />;
    </>
  );
}

Not Working on nextjs 13 app directory but perfectly works on pages directory? When we get support for app directory. Waiting for it ????Would be very happy to use this package instead of loading.tsx file.

commented

The following works (next 13.4.6, nprogress 0.2.0, react 18.2.0)

'use client';

import { useEffect } from 'react';

import NProgress from 'nprogress';

import StyledProgressBar from './styles';

type PushStateInput = [data: unknown, unused: string, url?: string | URL | null | undefined];

export default function ProgressBar() {
  useEffect(() => {
    NProgress.configure({ showSpinner: false });

    const handleAnchorClick = (event: MouseEvent) => {
      const targetUrl = (event.currentTarget as HTMLAnchorElement).href;
      const currentUrl = window.location.href;
      if (targetUrl !== currentUrl) {
        NProgress.start();
      }
    };

    const handleMutation: MutationCallback = () => {
      const anchorElements: NodeListOf<HTMLAnchorElement> = document.querySelectorAll('a[href]');

      anchorElements.forEach(anchor => anchor.addEventListener('click', handleAnchorClick));
    };

    const mutationObserver = new MutationObserver(handleMutation);

    mutationObserver.observe(document, { childList: true, subtree: true });

    window.history.pushState = new Proxy(window.history.pushState, {
      apply: (target, thisArg, argArray: PushStateInput) => {
        NProgress.done();
        return target.apply(thisArg, argArray);
      },
    });
  });

  return <StyledProgressBar />;
}

And then in your app/layout.tsx:

        <ThemeProvider>
              <ProgressBar />
               {children}
        </ThemeProvider>

              ....

I tried https://www.npmjs.com/package/nextjs-progressbar at first. I created a client component then wrapped my pages with it inside of layout.jsx file but it did not work.
I tried https://www.npmjs.com/package/nextjs-toploader as suggested and it works. I am still using a client component to wrap my pages

Any updates on next13? is there any way to pull this off?

I found this npm package: https://www.npmjs.com/package/nextjs-toploader

  • It perfectly works with the next js 13 in the layout.js for the app directory

Any updates on next13? is there any way to pull this off?

I found this npm package: https://www.npmjs.com/package/nextjs-toploader

  • It perfectly works with the next js 13 in the layout.js for the app directory

Thanks a lot

This seems a bit buggy. For me, it triggers the progress bar when I open a dropdown menu.

Many recommend using nextjs-toploader, but has anyone actually looked at how this package is implemented? No one in their right mind would use it in a somewhat serious project because of the endless bugs and performance issues.

https://github.com/TheSGJ/nextjs-toploader

this is perfectly working on the next js v13 app router/directory

I would highly recommend all to use it

Both solutions from above work with links only and not with router.push(). Anyone has an idea how to make it work with router.push() as well?

Both solutions from above work with links only and not with router.push(). Anyone has an idea how to make it work with router.push() as well?

Yeah I wish it would work for router.push() / router.replace()

<Link href={'/theway(static)'} prefetch={false} id='ID123' className='hidden'></Link>
document.getElementById('ID123')?.click()` // or ref and then click

This may work for static path.

https://github.com/TheSGJ/nextjs-toploader It's working but has a issue, it's keeps loading and never finish or completes..

TheSGJ/nextjs-toploader#56

https://github.com/TheSGJ/nextjs-toploader It's working but has a issue, it's keeps loading and never finish or completes..

TheSGJ/nextjs-toploader#56

Issue is fixed in NextJs v14.0.4