This application is being hosted on S3, entirely static (using next export
), and we're routing all 404 errors to index.html
in order to let those be handled on the client side so we can take advantage of dynamic routing. To handle this, I have the following in my _app.tsx
file:
const { asPath, pathname, ...router } = useRouter();
// check if redirect
React.useEffect(() => {
if (pathname === '/' && asPath !== pathname) {
router.replace(asPath, undefined, { shallow: true });
}
}, [asPath]);
This works, for the dynamic routing aspect, but it introduces a new bug: when I navigate to a page that actually doesn't exist, like /fffff
, there's an infinite loop of the app trying to reroute to /fffff
. Ideally, it would only try to reroute once, and then default to the 404.tsx
or _error.tsx
page. I've tried creating a stateful boolean like hasRedirected
and then just marking that as true in the useEffect
, but that didn't work because the page is actually refreshing and thus resetting state on each router.replace
call. How do I handle this error and break out of the loop?
update: The issue seems to be that when I call router.replace
, Next doesn't find a path to match /fffff
, so its default behavior is to try asking the server for the route by refreshing. I need to disable or intercept this behavior somehow.
The solution we ended up finding, which works quite well, uses session storage to store a hasRedirected
variable that gets deleted after being read. Here's the code:
React.useEffect(() => {
if (router.isReady) {
const isRedirect = pathname === '/' && asPath !== pathname;
if (sessionStorage.getItem('hasRedirected')) {
sessionStorage.removeItem('hasRedirected');
if (isRedirect) router.replace('/404');
} else if (isRedirect) {
sessionStorage.setItem('hasRedirected', 'true');
router.replace(asPath);
}
}
}, [asPath, pathname, router.isReady]);