Skip to content

Instantly share code, notes, and snippets.

@yuvalkarmi
Last active April 30, 2024 13:42
Show Gist options
  • 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>
}
@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