Replies: 42 comments 29 replies
-
Running tests against a development mode server is really uncommon and will allow you to ship bugs to production 🤔 |
Beta Was this translation helpful? Give feedback.
-
@timneutkens totally agree, we have multiple test steps before shipping to production: this will ideally run on pushes/PRs, then we have another one for our staging environment which will run as close to production as possible. As integration/e2e tests are both hard to write and slow to fix, we prefer fast iteration locally while making sure to run every test in staging before shipping to production |
Beta Was this translation helpful? Give feedback.
-
But even then the error overlay is shown when errors happen that would crash at runtime 🤔 So you'd fix them, if you're seeing the error overlay in other cases that'd be unexpected |
Beta Was this translation helpful? Give feedback.
-
@timneutkens actually, that's not always the case for us 😄 we also get the overlay for blocked networks calls (Failed to fetch) and I believe for errors that wouldn't affect the main application as well (think 3rd parties, but I'll have to double check that) These were errors that did not show up on the old overlay and are not part of the "proper" react application code |
Beta Was this translation helpful? Give feedback.
-
I also see the error overlay for failed network requests. |
Beta Was this translation helpful? Give feedback.
-
Because they're uncaught errors in your code, which is expected to show the overlay. |
Beta Was this translation helpful? Give feedback.
-
Agree, but that's why we might need an escape hatch or a way to control when to show the overlay. Sometimes, you're not in control of the code that is failing and you can't handle those errors at all |
Beta Was this translation helpful? Give feedback.
-
Although I agree that having this option would never be bad (more config is rarely bad, especially if it doesn't get in the way), I do agree that the error popping up should first be solved inside your code. I'm not saying that you haven't tried or anything, just that I think it's probably very rare for an uncaught exception to be uncatchable. You can always wrap it, right? Or for example I've had to dynamic import stuff that broke at import time, etc |
Beta Was this translation helpful? Give feedback.
-
Were you able to find a way to exclude certain exceptions? We use a library that is notorious for throwing exceptions but continues to work as intended. They are one of the only libraries and we can not just move away from it. It would be ideal to be able to handle these as an exclusion (they happen async/in a Promise). |
Beta Was this translation helpful? Give feedback.
-
I ran into this on a project I'm working on. In our case we had logic like so (it's worth noting that we had this in a lot of different files, and sometimes we were throwing 404s and other-times 400s): SomePage.getInitialProps = () => {
try {
const resp = await axios.get("/api/some-stuff");
return resp.data;
} catch (err) {
if (err.resp && err.resp.statusCode == 404) {
return Promise.reject(new NotFoundError());
}
// Fallback to a 500
return Promise.reject(err);
}
} We wrote our own This worked, but it forced us to add query string arguments to the ErrorPage.getInitialProps = (ctx: NextPageContext) => {
// Usually we take the status code associated with the error that brought the user here.
// We fallback to a 500 if we don't have one.
let statusCode = ctx.err && ctx.err.statusCode ? ctx.err.statusCode : 500;
// To make it easy to work on this page we let the user provide a status code via the query
// string.
const isNotProd = process.env.NODE_ENV !== 'production';
if (isNotProd && ctx.query.status) {
statusCode = parseInt(
Array.isArray(ctx.query.status) ? ctx.query.status[0] : ctx.query.status
);
}
if (ctx.res) {
ctx.res.statusCode = statusCode;
ctx.res.setHeader('Cache-Control', 'no-cache, no-store');
}
return { statusCode };
}; I realize this is a little different than the use-case mentioned in this discussion, as we weren't specifically looking to run integration tests. That said it's similar enough it felt worth mentioning here. Rather than adding "yet another option" (YAO 😅 ), what if using a custom // _error.tsx
import { ErrorOverlay } from 'next/error';
export default function ErrorPage({ statusCode, err }: ErrorProps) {
if (process.env.NODE_ENV !== "production" && statusCode === 500) {
return <ErrorOverlay {...err} />
}
return <MyCustomError status={statusCode} err={err} />
} This seems like a nice balance. If someone opts out by implementing a custom error page they'll probably find the ability to easily test the behavior more valuable than the affordances provided by the error overlay. |
Beta Was this translation helpful? Give feedback.
-
My team has also encountered this issue. We wrapped API code which might fail in a function much like await mightFail(() => {
callAPI()
successMessage()
}) This function would provide to the user an error message in a toast component. However, for flow control reasons, it's convenient for us that this function re-throws exceptions, in situations such as this: await callFunctionWhichCallsMightFail()
onFinish() // don't want to call this if the above throws! So we're left in a situation where we want to rethrow. But not too much, because this overlay would annoy us during development. I hope this is reconsidered. In the meantime, I created a hacky workaround for this issue, as follows: // in pages/_app.js
const noOverlayWorkaroundScript = `
window.addEventListener('error', event => {
event.stopImmediatePropagation()
})
window.addEventListener('unhandledrejection', event => {
event.stopImmediatePropagation()
})
`
// in a <Head> component:
{process.env.NODE_ENV !== 'production' && <script dangerouslySetInnerHTML={{ __html: noOverlayWorkaroundScript }} />} This is added in a script tag in the In our case, I've added conditions that check if the error went through our error notification functionality. Errors which didn't get shown as a toast, do show the overlay. |
Beta Was this translation helpful? Give feedback.
-
For those doing the same search as I am, implementing something identical to https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_document.js fixed this issue for me. My
I didn't need any |
Beta Was this translation helpful? Give feedback.
-
Should an error boundary prevent this popup from displaying? Because it isn't, and I thought an error boundary would be considered "caught". |
Beta Was this translation helpful? Give feedback.
-
@timneutkens If it's an easy adjustment, would it be possible to add an env var like Specifically it'd help with my use-case of trying to style some error pages/components where I'm intentionally throwing so I can see the error and design against it. It's frustrating having to close the NextJS/React overlay on every save. 🙃 |
Beta Was this translation helpful? Give feedback.
-
We have a tracking api call which fails in development, but works alright in production. We are fine with it failing in development because it does not impact any functionality. On production we already use a crash reporting tool which will tell us if the api fails. The error overlay keeps popping up on development mode. I do not want to have any special handling / check for development vs production in my codebase while trying to make this api call, and it would not make sense to handle this error as well, because I want the api to fail in production and get captured by the crash reporting setup. It is slightly annoying that we have to close it explicitly even though we want to ignore the errors. |
Beta Was this translation helpful? Give feedback.
-
You can add this to the .css file. nextjs-portal {
display: none;
} |
Beta Was this translation helpful? Give feedback.
-
The simplest would be an option directly understood by next to either show it by default, hide it, or minimize it. The request does not seem superfluous |
Beta Was this translation helpful? Give feedback.
-
This could maybe help for Cypress tests https://glebbahmutov.com/blog/close-popup/ |
Beta Was this translation helpful? Give feedback.
-
One point I haven't seen mentioned is that the error overlay makes it much harder to drop into the debugger to debug the issue. The only way is to check "Pause on caught exceptions", which then causes the debugger to break on a lot of exceptions caught by third-party libraries (e.g. feature detection). The debugger should be the primary UI when an uncaught exception occurs - it shows the stack trace and exception context much better than the error overlay can, and allows inspection of state. The overlay may be fine for simple errors, but it offers an inferior experience for everything else. For this reason e.g. physically hiding the overlay via CSS doesn't work - the exception is still caught by Next. Please add an option to disable the error overlay completely, or at least an option to rethrow the error that was caught. |
Beta Was this translation helpful? Give feedback.
-
If not for all the reasons mentioned above, something should be done in order to make Suspense usable. Right now any error thrown by a suspensable component will show the overlay, even though the intended way to handle errors like those is to use an error boundary. |
Beta Was this translation helpful? Give feedback.
-
My bet is this issue's gonna be kept ignored, then forgotten, and never taken action upon. Tough reality. |
Beta Was this translation helpful? Give feedback.
-
Clearly. It doesnt seem to make it high on their product board.
…On Thu, 14 Sep 2023 at 10:49, Kostiantyn Ko ***@***.***> wrote:
My bet is this issue's gonna be kept ignored, then forgotten, and never
taken action upon. Tough reality.
—
Reply to this email directly, view it on GitHub
<#13387 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAMYG5T22LNAJIR5XUDPZ5TX2KZHFANCNFSM4SOELWPQ>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
If you're importing a component and it has dynamic content, you could import it this way to avoid hydration and hence the error overlay.
|
Beta Was this translation helpful? Give feedback.
-
I tried with
and it worked fine |
Beta Was this translation helpful? Give feedback.
-
My preferred solution: keep the error box, but make it minimized by default to not interrupt the workflow. Next makes patching this difficult by having it in a separate package that is compiled from TS.
If you have Next.js version diff --git a/node_modules/next/dist/compiled/@next/react-dev-overlay/dist/client.js b/node_modules/next/dist/compiled/@next/react-dev-overlay/dist/client.js
index 6c32e39..7d0d3f3 100644
--- a/node_modules/next/dist/compiled/@next/react-dev-overlay/dist/client.js
+++ b/node_modules/next/dist/compiled/@next/react-dev-overlay/dist/client.js
@@ -295,7 +295,7 @@
.nextjs-container-build-error-body small {
color: var(--color-font);
}
-`},355:function(e,t,r){var n=this&&this.__createBinding||(Object.create?function(e,t,r,n){if(n===undefined)n=r;var o=Object.getOwnPropertyDescriptor(t,r);if(!o||("get"in o?!t.__esModule:o.writable||o.configurable)){o={enumerable:true,get:function(){return t[r]}}}Object.defineProperty(e,n,o)}:function(e,t,r,n){if(n===undefined)n=r;e[n]=t[r]});var o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:true,value:t})}:function(e,t){e["default"]=t});var a=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(e!=null)for(var r in e)if(r!=="default"&&Object.prototype.hasOwnProperty.call(e,r))n(t,e,r);o(t,e);return t};Object.defineProperty(t,"__esModule",{value:true});t.styles=t.Errors=void 0;const i=a(r(522));const l=r(851);const s=r(651);const u=r(732);const c=r(278);const d=r(120);const f=r(403);const m=r(233);const p=r(910);const b=r(865);const v=r(484);function getErrorSignature(e){const{event:t}=e;switch(t.type){case l.TYPE_UNHANDLED_ERROR:case l.TYPE_UNHANDLED_REJECTION:{return`${t.reason.name}::${t.reason.message}::${t.reason.stack}`}default:{}}const r=t;return""}const g=function HotlinkedText(e){const{text:t}=e;const r=/https?:\/\/[^\s/$.?#].[^\s)'"]*/i;return i.createElement(i.Fragment,null,r.test(t)?t.split(" ").map(((e,t,n)=>{if(r.test(e)){const o=r.exec(e);return i.createElement(i.Fragment,{key:`link-${t}`},o&&i.createElement("a",{href:o[0],target:"_blank",rel:"noreferrer noopener"},e),t===n.length-1?"":" ")}return t===n.length-1?i.createElement(i.Fragment,{key:`text-${t}`},e):i.createElement(i.Fragment,{key:`text-${t}`},e," ")})):t)};const h=function Errors({errors:e}){const[t,r]=i.useState({});const[n,o]=i.useMemo((()=>{let r=[];let n=null;for(let o=0;o<e.length;++o){const a=e[o];const{id:i}=a;if(i in t){r.push(t[i]);continue}if(o>0){const t=e[o-1];if(getErrorSignature(t)===getErrorSignature(a)){continue}}n=a;break}return[r,n]}),[e,t]);const a=i.useMemo((()=>n.length<1&&Boolean(e.length)),[e.length,n.length]);i.useEffect((()=>{if(o==null){return}let e=true;(0,f.getErrorByType)(o).then((t=>{if(e){r((e=>({...e,[t.id]:t})))}}),(()=>{}));return()=>{e=false}}),[o]);const[l,p]=i.useState("fullscreen");const[h,y]=i.useState(0);const x=i.useCallback((e=>{e?.preventDefault();y((e=>Math.max(0,e-1)))}),[]);const E=i.useCallback((e=>{e?.preventDefault();y((e=>Math.max(0,Math.min(n.length-1,e+1))))}),[n.length]);const _=i.useMemo((()=>n[h]??null),[h,n]);i.useEffect((()=>{if(e.length<1){r({});p("hidden");y(0)}}),[e.length]);const w=i.useCallback((e=>{e?.preventDefault();p("minimized")}),[]);const O=i.useCallback((e=>{e?.preventDefault();p("hidden")}),[]);const j=i.useCallback((e=>{e?.preventDefault();p("fullscreen")}),[]);if(e.length<1||_==null){return null}if(a){return i.createElement(c.Overlay,null)}if(l==="hidden"){return null}if(l==="minimized"){return i.createElement(d.Toast,{className:"nextjs-toast-errors-parent",onClick:j},i.createElement("div",{className:"nextjs-toast-errors"},i.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},i.createElement("circle",{cx:"12",cy:"12",r:"10"}),i.createElement("line",{x1:"12",y1:"8",x2:"12",y2:"12"}),i.createElement("line",{x1:"12",y1:"16",x2:"12.01",y2:"16"})),i.createElement("span",null,n.length," error",n.length>1?"s":""),i.createElement("button",{"data-nextjs-toast-errors-hide-button":true,className:"nextjs-toast-errors-hide-button",type:"button",onClick:e=>{e.stopPropagation();O()},"aria-label":"Hide Errors"},i.createElement(b.CloseIcon,null))))}const k=["server","edge-server"].includes((0,m.getErrorSource)(_.error)||"");return i.createElement(c.Overlay,null,i.createElement(s.Dialog,{type:"error","aria-labelledby":"nextjs__container_errors_label","aria-describedby":"nextjs__container_errors_desc",onClose:k?undefined:w},i.createElement(s.DialogContent,null,i.createElement(s.DialogHeader,{className:"nextjs-container-errors-header"},i.createElement(u.LeftRightDialogHeader,{previous:h>0?x:null,next:h<n.length-1?E:null,close:k?undefined:w},i.createElement("small",null,i.createElement("span",null,h+1)," of"," ",i.createElement("span",null,n.length)," unhandled error",n.length<2?"":"s")),i.createElement("h1",{id:"nextjs__container_errors_label"},k?"Server Error":"Unhandled Runtime Error"),i.createElement("p",{id:"nextjs__container_errors_desc"},_.error.name,":"," ",i.createElement(g,{text:_.error.message})),k?i.createElement("div",null,i.createElement("small",null,"This error happened while generating the page. Any console logs will be displayed in the terminal window.")):undefined),i.createElement(s.DialogBody,{className:"nextjs-container-errors-body"},i.createElement(v.RuntimeError,{key:_.id.toString(),error:_})))))};t.Errors=h;t.styles=(0,p.noop)`
+`},355:function(e,t,r){var n=this&&this.__createBinding||(Object.create?function(e,t,r,n){if(n===undefined)n=r;var o=Object.getOwnPropertyDescriptor(t,r);if(!o||("get"in o?!t.__esModule:o.writable||o.configurable)){o={enumerable:true,get:function(){return t[r]}}}Object.defineProperty(e,n,o)}:function(e,t,r,n){if(n===undefined)n=r;e[n]=t[r]});var o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:true,value:t})}:function(e,t){e["default"]=t});var a=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(e!=null)for(var r in e)if(r!=="default"&&Object.prototype.hasOwnProperty.call(e,r))n(t,e,r);o(t,e);return t};Object.defineProperty(t,"__esModule",{value:true});t.styles=t.Errors=void 0;const i=a(r(522));const l=r(851);const s=r(651);const u=r(732);const c=r(278);const d=r(120);const f=r(403);const m=r(233);const p=r(910);const b=r(865);const v=r(484);function getErrorSignature(e){const{event:t}=e;switch(t.type){case l.TYPE_UNHANDLED_ERROR:case l.TYPE_UNHANDLED_REJECTION:{return`${t.reason.name}::${t.reason.message}::${t.reason.stack}`}default:{}}const r=t;return""}const g=function HotlinkedText(e){const{text:t}=e;const r=/https?:\/\/[^\s/$.?#].[^\s)'"]*/i;return i.createElement(i.Fragment,null,r.test(t)?t.split(" ").map(((e,t,n)=>{if(r.test(e)){const o=r.exec(e);return i.createElement(i.Fragment,{key:`link-${t}`},o&&i.createElement("a",{href:o[0],target:"_blank",rel:"noreferrer noopener"},e),t===n.length-1?"":" ")}return t===n.length-1?i.createElement(i.Fragment,{key:`text-${t}`},e):i.createElement(i.Fragment,{key:`text-${t}`},e," ")})):t)};const h=function Errors({errors:e}){const[t,r]=i.useState({});const[n,o]=i.useMemo((()=>{let r=[];let n=null;for(let o=0;o<e.length;++o){const a=e[o];const{id:i}=a;if(i in t){r.push(t[i]);continue}if(o>0){const t=e[o-1];if(getErrorSignature(t)===getErrorSignature(a)){continue}}n=a;break}return[r,n]}),[e,t]);const a=i.useMemo((()=>n.length<1&&Boolean(e.length)),[e.length,n.length]);i.useEffect((()=>{if(o==null){return}let e=true;(0,f.getErrorByType)(o).then((t=>{if(e){r((e=>({...e,[t.id]:t})))}}),(()=>{}));return()=>{e=false}}),[o]);const[l,p]=i.useState("minimized");const[h,y]=i.useState(0);const x=i.useCallback((e=>{e?.preventDefault();y((e=>Math.max(0,e-1)))}),[]);const E=i.useCallback((e=>{e?.preventDefault();y((e=>Math.max(0,Math.min(n.length-1,e+1))))}),[n.length]);const _=i.useMemo((()=>n[h]??null),[h,n]);i.useEffect((()=>{if(e.length<1){r({});p("hidden");y(0)}}),[e.length]);const w=i.useCallback((e=>{e?.preventDefault();p("minimized")}),[]);const O=i.useCallback((e=>{e?.preventDefault();p("hidden")}),[]);const j=i.useCallback((e=>{e?.preventDefault();p("fullscreen")}),[]);if(e.length<1||_==null){return null}if(a){return i.createElement(c.Overlay,null)}if(l==="hidden"){return null}if(l==="minimized"){return i.createElement(d.Toast,{className:"nextjs-toast-errors-parent",onClick:j},i.createElement("div",{className:"nextjs-toast-errors"},i.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"},i.createElement("circle",{cx:"12",cy:"12",r:"10"}),i.createElement("line",{x1:"12",y1:"8",x2:"12",y2:"12"}),i.createElement("line",{x1:"12",y1:"16",x2:"12.01",y2:"16"})),i.createElement("span",null,n.length," error",n.length>1?"s":""),i.createElement("button",{"data-nextjs-toast-errors-hide-button":true,className:"nextjs-toast-errors-hide-button",type:"button",onClick:e=>{e.stopPropagation();O()},"aria-label":"Hide Errors"},i.createElement(b.CloseIcon,null))))}const k=["server","edge-server"].includes((0,m.getErrorSource)(_.error)||"");return i.createElement(c.Overlay,null,i.createElement(s.Dialog,{type:"error","aria-labelledby":"nextjs__container_errors_label","aria-describedby":"nextjs__container_errors_desc",onClose:k?undefined:w},i.createElement(s.DialogContent,null,i.createElement(s.DialogHeader,{className:"nextjs-container-errors-header"},i.createElement(u.LeftRightDialogHeader,{previous:h>0?x:null,next:h<n.length-1?E:null,close:k?undefined:w},i.createElement("small",null,i.createElement("span",null,h+1)," of"," ",i.createElement("span",null,n.length)," unhandled error",n.length<2?"":"s")),i.createElement("h1",{id:"nextjs__container_errors_label"},k?"Server Error":"Unhandled Runtime Error"),i.createElement("p",{id:"nextjs__container_errors_desc"},_.error.name,":"," ",i.createElement(g,{text:_.error.message})),k?i.createElement("div",null,i.createElement("small",null,"This error happened while generating the page. Any console logs will be displayed in the terminal window.")):undefined),i.createElement(s.DialogBody,{className:"nextjs-container-errors-body"},i.createElement(v.RuntimeError,{key:_.id.toString(),error:_})))))};t.Errors=h;t.styles=(0,p.noop)`
.nextjs-container-errors-header > h1 {
font-size: var(--size-font-big);
line-height: var(--size-font-bigger); |
Beta Was this translation helpful? Give feedback.
-
Please invest 5 minutes and add an env var to disable the overlay, thank you. |
Beta Was this translation helpful? Give feedback.
-
Hiding the overlay is not enough. When this happens the browser's console is flooded with next-js related logs hampering debugging the cause. |
Beta Was this translation helpful? Give feedback.
-
I've used @sesm's solution for Next v12. Here are updated steps for Next v14.2.3:
To completely hide overlays, replace "minimized" with "hidden". In case you run into an issue with patch-package, see this related discussion: ds300/patch-package#300 (comment) |
Beta Was this translation helpful? Give feedback.
-
How to hidden overlay when environment isn't develepment? |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Feature request
Is your feature request related to a problem? Please describe.
Hey, the new error overlay is great and I am very thankful of you guys for adding such a feature.
The problem: it's not that great when running automated tests (with something like Cypress). Any unhandled error/promise will cause the overlay to pop up (it was not the case with the previous react error overlay), causing the test to fail - even when the error originates from some third party code that we cannot handle ourselves
Describe the solution you'd like
Either a flag to disable the overlay altogether or a global hook to handle the exception ourselves and decide if it's worth throwing or not, like Cypress does
Describe alternatives you've considered
We have opted to run the tests in prod mode for now, but that's something we'd prefer not to do, because of the development mode goodies that we can use for debugging.
Additional context
In our case, the error being thrown is a fetch error from a third party library to a domain we block in our test config
Beta Was this translation helpful? Give feedback.
All reactions