This is one of those browser-safety limitations:
You cannot prevent or replace the native beforeunload
confirmation popup with your own UI.
Browsers deliberately don’t allow custom modals or asynchronous code in beforeunload
.
The only allowed behavior is either:
Let the page unload silently, or
Show the browser’s built-in confirmation dialog (with its own text/UI).
That’s why setting e.preventDefault()
or e.returnValue
just shows the default browser popup—you can’t override it with a React modal.
If your goal is to warn users about unsaved form changes and show a custom modal, you’ll need to intercept navigation inside your app, not the hard reload:
Intercept in-app navigation (Next.js Router):
import { useRouter } from "next/router";
import { useEffect } from "react";
export default function MyForm() {
const router = useRouter();
const isDirty = true; // track whether form has unsaved changes
useEffect(() => {
const handleRouteChangeStart = (url: string) => {
if (isDirty && !confirm("You have unsaved changes. Leave anyway?")) {
router.events.emit("routeChangeError");
// throw to cancel navigation
throw "Route change aborted.";
}
};
router.events.on("routeChangeStart", handleRouteChangeStart);
return () => {
router.events.off("routeChangeStart", handleRouteChangeStart);
};
}, [isDirty]);
return <form>…</form>;
}
Here you can replace
confirm
with your own React modal state.
For full page reload (F5, Ctrl+R, closing tab):
You’re stuck with the native confirmation dialog.
No way to cancel reload and show your React modal—browsers block this for security reasons.
✅ Bottom line:
Inside SPA navigation (Next.js routes) → you can intercept and show your own modal.
Hard reload / tab close → you can only trigger the default browser popup, not your own.