Search code examples
reactjsnext.jsurl-routingdynamic-routing

How do I break the infinite redirect loop I've created in Next JS?


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.


Solution

  • 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]);