apal21 / nextjs-progressbar

A simple Next.js progressbar component using NProgress.
https://www.npmjs.com/package/nextjs-progressbar
MIT License
776 stars 61 forks source link

Next 13 app dir support #86

Open Fedeorlandau opened 1 year ago

Fedeorlandau commented 1 year ago

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: https://github.com/vercel/next.js/discussions/41745#discussioncomment-3986452

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

Thanks!

HaNdTriX commented 1 year ago

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

AlanMorel commented 1 year ago

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

Pyakz commented 1 year ago

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

jimapl commented 1 year ago

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

mairura commented 1 year ago

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

imranbarbhuiya commented 1 year ago

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

karlhorky commented 1 year ago

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;
}
anamiikajha commented 1 year ago

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
jimapl commented 1 year ago

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

Pyakz commented 1 year ago

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?

anamiikajha commented 1 year ago

Yeah, works for me very well.

Pyakz commented 1 year ago

Yeah, works for me very well.

i put it in my _layout, it doesnt work

EdsonLucasbd commented 1 year ago

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

apal21 commented 1 year ago

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.

karlhorky commented 1 year ago

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

codinginflow commented 1 year ago

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.

SRX9 commented 1 year ago

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} />;
    </>
  );
}
ChetSocio commented 1 year ago

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.

pimmee commented 1 year ago

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>

              ....
lul-g commented 1 year ago

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
vikramsamak commented 1 year ago

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

monolithed commented 1 year ago

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.

Darpan-favfly commented 1 year ago

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

IlirEdis commented 11 months ago

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?

Bogd1y commented 9 months ago

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.

jigsaxis commented 8 months ago

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

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

jigsaxis commented 8 months ago

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