Next.js: Endless loop when _error render throws on client side

Created on 9 Apr 2019  Â·  10Comments  Â·  Source: vercel/next.js

Bug report

Describe the bug

When using a custom _error page, Next can enter an infinite loop if _error render throws on client side (with a production build).

To Reproduce

Repro here : https://github.com/dorian-marchal/next-repro/tree/repro-6973

In a terminal:

$ git clone -b repro-6973 https://github.com/dorian-marchal/next-repro
Cloning into 'next-repro'...

$ cd next-repro/

$ node -v
v10.14.1

$ npm install

$ npm ls next
[email protected] /tmp/next-repro
└── [email protected]

$ npm run build && npm run start
...
> Ready on http://localhost:3000

In your browser:

  • Go to http://localhost:3000/
  • Open your browser devtools
  • Click on "Client side nav"
  • See the app entering an endless loop (stopped after 20 iterations).

Expected behavior

On client side, if _error throws, I expect an "Internal Error" page (this is what happens when _error throw during server side rendering).

I suppose that Next could force a page reload in this case.

System information

  • OS: Ubuntu 18.04
  • Version of Next.js: 8.0.4

Additional context

In our project, this error happens when _error getInitialProps fails to retrieve some dependencies (Error.render tries to access an undefined property).

good first issue bug needs investigation

Most helpful comment

Hi guy 👋

I did a bit of digging in the next.js/packages/next code and found that:

In this renderError function we can see why it does not crash in dev mode because it let the HMR handle the error. But in production what happens is that we go past that if, and load the "_error" page and re-render it right away.

There you have the error loop. If the error is thrown in a custom _error page, we reload it, re-render it, it throws again, we load it, etc. you get the idea.

One solution would be to check if we are on the _error page, then avoid the subsequent pageLoad calls and/or show another, non-customizable, error page (fallback on the next _error page maybe with a nice error message in the console ?)
Unfortunately, as we are above the RouterProvider, I don't see a reliable way to know if the currently rendered page is /_error or not. Or maybe by calling the getRouter().pathname in renderError, as it is a singleton, but this gives me goosebumps.

I'd love to address this issue if the above "solution" is a good lead, or any other better solution.

Hope it helps, Cheers

All 10 comments

Possible workaround, using an error boundary: https://github.com/dorian-marchal/next-repro/blob/workaround-6973/pages/_error.js

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return "Internal Error.";
    }

    return this.props.children;
  }
}

const ErrorTemplate = () => {
  // ReferenceError: foo is not defined.
  foo;
  return "OK";
};

class Error extends React.Component {
  render() {
    return (
      <ErrorBoundary>
        {/* If ErrorTemplate throws, the boundary will catch it. */}
        <ErrorTemplate {...this.props} />
      </ErrorBoundary>
    );
  }
}

^ We are running into this same error on our side. The workaround provided doesn't appear to be working for us, when we implement it verbatim.

@armenr Here's what worked for me:

export default class Error extends React.Component {

    static getInitialProps = ({ res, err }) => ({ statusCode: res ? res.statusCode : err ? err.statusCode : null });

    render = () => {
        return (
            <p>Page content</p>
        );
    };

    componentDidMount = () => {
        if (this.props.statusCode === 200) window.location = "/error";
    };

};

It's a little hacky but it gets the job done ¯\_(ツ)_/¯

Same error here. None of the solutions above did the trick for me =/

This is a really worrisome bug. Happens to me on every single error.

Well it's not super worrisome, you're customizing the error page and introducing another error (runtime error) into it, which is more like a pretty big edge case. We'll fix it though (or you're welcome to open a PR).

Hi guy 👋

I did a bit of digging in the next.js/packages/next code and found that:

In this renderError function we can see why it does not crash in dev mode because it let the HMR handle the error. But in production what happens is that we go past that if, and load the "_error" page and re-render it right away.

There you have the error loop. If the error is thrown in a custom _error page, we reload it, re-render it, it throws again, we load it, etc. you get the idea.

One solution would be to check if we are on the _error page, then avoid the subsequent pageLoad calls and/or show another, non-customizable, error page (fallback on the next _error page maybe with a nice error message in the console ?)
Unfortunately, as we are above the RouterProvider, I don't see a reliable way to know if the currently rendered page is /_error or not. Or maybe by calling the getRouter().pathname in renderError, as it is a singleton, but this gives me goosebumps.

I'd love to address this issue if the above "solution" is a good lead, or any other better solution.

Hope it helps, Cheers

Is this still a bug as of the latest Next.js version? Could someone please check?

v9.4.2, still happens.

9.5.3 not fixed

Was this page helpful?
0 / 5 - 0 ratings

Related issues

olifante picture olifante  Â·  3Comments

lixiaoyan picture lixiaoyan  Â·  3Comments

formula349 picture formula349  Â·  3Comments

jesselee34 picture jesselee34  Â·  3Comments

havefive picture havefive  Â·  3Comments