Skip to content

Instantly share code, notes, and snippets.

@yuvalkarmi
Last active April 30, 2024 13:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yuvalkarmi/ab7944b2da693c71cf697db6a47e5b5d to your computer and use it in GitHub Desktop.
Save yuvalkarmi/ab7944b2da693c71cf697db6a47e5b5d to your computer and use it in GitHub Desktop.
How to block a user from navigating away in NextJS when clicking on Link component
/*
In standard web apps, when users navigate away to a different page or website or attempt to refresh a page,
we can use the window event listener `beforeunload` to help users avoid losing the work.
However, when a user clicks on a native NextJS <Link> component, the navigation is not intercepted
because behind the scenes, no page changes are actually happening.
To intercept user navigation in a NextJS app, we can subscribe to the
router routeChangeError event and throw an error.
Note: we must throw an Error string - not a `new Error()` - which is why in the code below, we have
// eslint-disable-next-line no-throw-literal
above the line throwing the error.
*/
import Router from "next/router";
import { useEffect } from 'react';
const YourComponent = () => {
// Relevent code begin
const shouldBlockNavigation = true; // set this up however you want
useEffect(() => {
const nativeBrowserHandler = (event) => {
if (shouldBlockNavigation) {
event.preventDefault();
event.returnValue = '';
}
};
const nextNavigationHandler = (url) => {
if (shouldBlockNavigation) {
if (!window.confirm('Navigate away? Changes you made may not be saved.')) {
Router.events.emit('routeChangeError')
// eslint-disable-next-line no-throw-literal
throw "Abort route change by user's confirmation."
}
}
};
window.addEventListener('beforeunload', nativeBrowserHandler);
Router.events.on("beforeHistoryChange", nextNavigationHandler);
// On component unmount, remove the event listener
return () => {
window.removeEventListener('beforeunload', nativeBrowserHandler);
Router.events.off("beforeHistoryChange", nextNavigationHandler);
};
}, [shouldBlockNavigation]);
// Relevent code end
return <div>Your Component</div>
}
@yuvalkarmi
Copy link
Author

yuvalkarmi commented Jun 21, 2023

Note that event.returnValue = ''; maybe no longer be necessary as we are using event.preventDefault()
Thanks @trdbau

@tranduybau
Copy link

@yuvalkarmi love to see that. 🚀

@huongnguyenduc
Copy link

is this solution support nextjs 13 app route?

@yuvalkarmi
Copy link
Author

@huongnguyenduc I tested this on Next 13.1.6 and can confirm it works on that version.

@huongnguyenduc
Copy link

@yuvalkarmi do you test this on Pages route or new App route?

@tranduybau
Copy link

@huongnguyenduc can you try it and tell us the result? Thx

@yuvalkarmi
Copy link
Author

Not sure what you mean by new app route. I have it set up on a page.

@tranduybau
Copy link

@yuvalkarmi he is asking about the new App Router in Next 13.4.x up.

@huongnguyenduc I think you should try it yourself. The time you spent for asking is more than the time you test in the App Route. You already have all the code. Don't spam.

@huongnguyenduc
Copy link

@trdbau My project is using the new App route, I tried and found that in the new App route, it uses a different Router (import from next/navigate) and has not supported events yet like Router from next/router in the Pages route. So I'm still looking/waiting for an upcoming change / another solution for it.

@tranduybau
Copy link

@huongnguyenduc yah I know bro. I saw that from the start. But in that case, you better commented something like : Didn't work on my app using new App Router. The version is 13.4.X. Can you help me with that?. Here we don't know who you are and don't care about it. Be patient and polite. Sure, You can add my telegram and I can help you. Here you go: https://t.me/bautd

@huongnguyenduc
Copy link

@trdbau Oh sorry if I commented something badly. Thank you very much for your feedback!! I appreciate your quick responses so much ^^

@tranduybau
Copy link

tranduybau commented Jun 22, 2023

@huongnguyenduc NP. Have a good day. Tell us if you find something in the future 😄

@DiegoMcDipster
Copy link

DiegoMcDipster commented Jun 29, 2023

I'd used one of the previous (pretty messy and hacky) solutions but that suddenly stopped since upgrading to Next 13. So, I came back to the issue and found this much cleaner solution.

However, I do have an issue that I'm hoping you can help me with. In my _app.ts I am using the router events for my loading animation, like so:

useEffect(() => {
    const handleRouteChangeStart = () => {
      setIsLoading(true);
    };

    const handleRouteChangeComplete = () => {
      setIsLoading(false);
    };

    const handleRouteChangeError = () => {
      setIsLoading(false);
    };

    router.events.on("routeChangeStart", handleRouteChangeStart);
    router.events.on("routeChangeComplete", handleRouteChangeComplete);
    router.events.on("routeChangeError", handleRouteChangeError);

    return () => {
      router.events.off("routeChangeStart", handleRouteChangeStart);
      router.events.off("routeChangeComplete", handleRouteChangeComplete);
      router.events.off("routeChangeError", handleRouteChangeError);
    };
  }, [router.events]);

This seems to take precedence over your event handlers on the component. So,

  1. browser back button: does NOT display the route change warning
  2. next/router.back() does NOT display the route change warning
  3. browser refresh DOES display the warning

I'm pretty new to all this, so please let me know if my loading animation code is not a best practice. Otherwise, what could be a possible solution?

@sahmed007
Copy link

So frickin' glad I came across this. This worked for me on Next 13.2.4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment