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