How to intercept route changes? #12348
Replies: 12 comments 17 replies
-
I recently used the workaround in this thread, but would love to see a proper feature implemented. Router.events.on('routeChangeStart', () => {
// Cancel routeChange event by erroring
// See https://github.com/zeit/next.js/issues/2476
Router.events.emit('routeChangeError')
throw `routeChange aborted. This error can be safely ignored - https://github.com/zeit/next.js/issues/2476.`
}) |
Beta Was this translation helpful? Give feedback.
-
+1 on this topic. It is highly desirable to have route change abort if Router.events.on('beforeHistoryChange', (route: string) => {
if (route.includes('PRIVATE_ROUTE_NAME')) {
return false;
}
return true;
}); |
Beta Was this translation helpful? Give feedback.
-
Is there anything in NextJS that reliably handles this type of behavior? I'm trying to do something similar to what the
|
Beta Was this translation helpful? Give feedback.
-
The solution we ended up using was globally listening to events. We created a const handleClick = event => {
if ([/* ... blocked paths ... */].includes(router.pathname)) {
const eventWillRedirect = event
.composedPath()
.filter(el => el.nodeName === 'A');
// interceptionConfig defines custom rules and settings for route interception
const config = interceptionConfig();
if (eventWillRedirect.length && config.shouldIntercept) {
const path = eventWillRedirect[0].attributes.href.value;
// custom logic for our purposes
const areaPrefix = `/areas/${areaSlug}`;
const urlIsInsideArea = path.indexOf(areaPrefix) === 0;
if (urlIsInsideArea) {
event.preventDefault();
// opens route interceptor modal which has a button to continue or to abort
openModal({ contentName: 'routeInterceptor' });
setInterceptedRoute(path);
setInterceptedAsPath(getGenericPath(path));
setInterceptionReason(config.reason);
}
}
}
}; And then set up the listener in a useEffect(() => {
document.addEventListener('click', handleClick, { capture: true });
return () =>
document.removeEventListener('click', handleClick, { capture: true });
}, [/* ... deps from Redux */, handleClick]); |
Beta Was this translation helpful? Give feedback.
-
Thanks for the discussion and the possible workarounds. const handleWindowClose = (e) => {
e.preventDefault();
return (e.returnValue = 'Are you sure you want to leave?');
};
let leaveConfirmed = false;
export const useLeavePrevention = () => {
const router = useRouter();
// Use beforeunload to prevent closing the tab, refreshing the page or moving outside the Next app
useEffect(() => {
window.addEventListener('beforeunload', handleWindowClose);
return () => {
window.removeEventListener('beforeunload', handleWindowClose);
};
});
// Use routeChangeStart to prevent navigation inside of the Next app
// Uses a module variable to bypass the confirm, otherwise we would be in a loop
router.events.on('routeChangeStart', () => {
if (leaveConfirmed) return;
if (window.confirm('Are you sure you want to leave?'))
leaveConfirmed = true;
else {
router.events.emit('routeChangeError');
throw 'routeChange aborted.';
}
});
// Set the module variable to false on component mount
useEffect(() => {
leaveConfirmed = false;
}, []);
}; I don't have any parameters so it always asks and this global module variable could bring up some issues, but for my case it works and you get the rough idea. |
Beta Was this translation helpful? Give feedback.
-
Might be an alternative: #2476 (comment) |
Beta Was this translation helpful? Give feedback.
-
Hi, I am working on a more robust alternative. I don't like the idea of coupling too much this feature with the framework. Here is what I am implementing currently Basically, I split the process in multiple steps:
If you switch framework in 5 years, no problem, there is no coupling (in our case we strive to support any React based framework so Gatsby, Next, our legacy Meteor version of Vulcan). |
Beta Was this translation helpful? Give feedback.
-
Heyy, I'm using next.js, this code works fine when I use it in [path].js , and when route is being changed from '/something' to '/somethingelse', |
Beta Was this translation helpful? Give feedback.
-
I am trying to go other url when there is no history useEffect(() => {
//console.log(historyIndex);
const beforeHistoryChange = () => {
if (historyIndex === 0) {
router.push('/campaigns2');
}
};
router.events.on('beforeHistoryChange', beforeHistoryChange);
return () => {
router.events.off('beforeHistoryChange', beforeHistoryChange);
};
}, []); |
Beta Was this translation helpful? Give feedback.
-
Support for App Router |
Beta Was this translation helpful? Give feedback.
-
How to use router.beforePopState in NextJs 13/14. Please help, Thank you. |
Beta Was this translation helpful? Give feedback.
-
Intercept route changes at the NextJS app router modeDemo:[codeSondbox] cf6e2e9c42a4f29b1dacadffb58c9a1f_723815601830_v_1702122801840414.mp4source code: https://github.com/cgfeel/next.v2/tree/master/routing-file/src/app/leaving/proxy Use this Provider in your layout at the app root directory:https://github.com/cgfeel/next.v2/blob/master/routing-file/src/components/proxyProvider/index.tsx 'use client'
import { usePathname, useSearchParams } from "next/navigation";
import Script from "next/script";
import { FC, PropsWithChildren, createContext, useEffect, useState } from "react";
const ProxyContext = createContext<ProxyInstance>([undefined, () => {}]);
const ProxyProvider: FC<PropsWithChildren<{}>> = ({ children }) => {
const [tips, setTips] = useState<string|undefined>();
const msg = tips === undefined ? tips : (tips||'Are you sure want to leave this page?');
const pathname = usePathname();
const searchParams = useSearchParams();
const url = [pathname, searchParams].filter(i => i).join('?');
useEffect(() => {
setTips(undefined);
}, [url, setTips]);
useEffect(() => {
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
if (msg === undefined) return msg;
event.preventDefault();
event.returnValue = msg;
return msg;
};
const script = document.getElementById('proxy-script');
if (script) {
script.dataset.msg = msg||'';
script.dataset.href = location.href;
}
window.addEventListener("beforeunload", handleBeforeUnload);
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
}
}, [msg]);
return (
<ProxyContext.Provider
value={[msg, setTips]}
>
<Script
strategy="afterInteractive"
id="proxy-script"
dangerouslySetInnerHTML={{
__html: `(() => {
const originalPushState = history.pushState.bind(history);
let currentPoint = 0;
let point = 0;
window.history.pushState = function(state, title, url) {
state.point = ++point;
currentPoint = point;
originalPushState(state, title, url);
};
const originalReplaceState = history.replaceState.bind(history);
window.history.replaceState = function(state, title, url) {
state.point = currentPoint;
originalReplaceState(state, title, url);
};
window.addEventListener('popstate', function (event) {
const { state: nextState } = event;
const isback = currentPoint > nextState.point;
currentPoint = nextState.point;
const script = document.getElementById('proxy-script');
if (!script || location.href === script.dataset.href) return;
const msg = script.dataset.msg||'';
const confirm = msg == '' ? true : window.confirm(msg);
if (!confirm) {
event.stopImmediatePropagation();
isback ? history.forward() : history.back();
}
});
})()`,
}}
></Script>
{children}
</ProxyContext.Provider>
);
};
export type ProxyInstance = [
string|undefined, (tips?: string) => void
]
export { ProxyContext };
export default ProxyProvider; |
Beta Was this translation helpful? Give feedback.
-
Based on same React state (streaming app, so e.g. the user currently streaming), we need to intercept the navigation of the user. The user needs to be prompted, whether he wants to continue leaving the page, or not. If he is sure he wants to leave the page, we need to continue with the navigation. If not, we need to cancel the navigation. How do you do this with Next.js?
I have seen a big discussion about this in this PR #5377 that (IMO) has been closed prematurely. Is there an official way of achieving the aforementioned behavior? If not, can we make this a feature request?
One suggested solution has been to use custom link tags, but some of our navigation happens inside of Redux Sagas programatically. So we need to somehow be able to act based on events from the Next.js
Router
and cancel if necessary.API Example
Beta Was this translation helpful? Give feedback.
All reactions