When using a custom _error page, Next can enter an infinite loop if _error render throws on client side (with a production build).
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:
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.
In our project, this error happens when _error getInitialProps fails to retrieve some dependencies (Error.render tries to access an undefined property).
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
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
/_erroror 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