vercel / next.js

The React Framework
https://nextjs.org
MIT License
124.84k stars 26.65k forks source link

Docs: How to simulate an app/global-error.js | tsx #46964

Open icazemier opened 1 year ago

icazemier commented 1 year ago

What is the improvement or update you wish to see?

Would be nice, to somehow trigger an error which shows this page

Is there any context that might help us understand?

For testing purposes

Does the docs page already exist? Please link to it.

https://beta.nextjs.org/docs/api-reference/file-conventions/error#global-errorjs

yannbolliger commented 1 year ago

You can simply throw an error in your root/top-level layout.tsx and launch your app with next start (in dev you'll see the dev error view).

icazemier commented 1 year ago

You can simply throw an error in your root/top-level layout.tsx and launch your app with next start (in dev you'll see the dev error view).

Dear @yannbolliger ,

I tried something like:

export default function RootLayout({ children }: { children: React.ReactNode }) {
// 🙏🏼
throw new Error('Whoops');

  return (
    <html lang="en" translate="no">
....

in : src/app/layout.tsx (appDir:true)

or even with a setTimeout around it, but I just can't seem to simulate a situation where I see the global-error page popping up.

Could you give me an example?

RafalChojnowskiPagepro commented 1 year ago

This example fails for me too

yannbolliger commented 1 year ago

Did you use the production build next start?

icazemier commented 11 months ago

Hi NextJS team,

I'm still struggling to "really" see the global-error page live in action.

Seems related to this (locked and limited conversation to collaborators) issue: https://github.com/vercel/next.js/issues/46572

I can reproduce the fact global-error.tsx is (still) not working with the following steps to reproduce: (Pushed here: https://github.com/icazemier/nextjs-global-error-issue)

Install nextjs (corresponding to the docs)

image

Create global-error.tsx in /app (as documented)

"use client";

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  );
}

Throw a async error in the root layout file


import "./globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  // Throw an error here!
  setTimeout(() => {
    throw new Error("Whoops");
  }, 2000);

  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}

Build and run the project (production variant)

npm run build

image

npm start

Navigate to the root page in the browser and wait for the error

Front end doesn't give a clue (no global-error triggererd)

image

Terminal doesn't show anything

image

Build and run the project in development mode

Nothing in the front end either, but in the terminal it does show the error??

image

Obviously there is something wrong, because no global error is triggered.

Or I'm doing weird things (which often is the case .... 😅).

But my ticket/request still stands.

icazemier commented 11 months ago

Not sure, but seeing several related issues, possibly... Tagging:

icazemier commented 11 months ago

In the meantime I managed to get the error boundary working!

Partially because I didn't understand why the boundaries where not catching them... I read about it here: https://semaphoreci.com/blog/error-handling-layer-react#limitations-of-this-approach

Made a error catch provider to at least trigger the error boundary and showing itself

See: https://github.com/icazemier/nextjs-global-error-issue/tree/22da1965208eae721aafa3ca8e2ef80ad5cdc468

(Still not able to simulate the global-error.tsx)

ADTC commented 8 months ago

How to simulate an app/global-error.js | tsx

Things you must understand first:

Setting up:

Create a global-error.tsx file as per the docs and the above tips.

Button to reload page in the global-error.tsx file:

Skip this if you don't need a Reload button. But if you do, since we cannot use the useRouter hook here, we depend on regular Javascript to do a full-page refresh:

<button onClick={() => window.location.reload()}>Reload page</button>

Note: The reset: () => void parameter doesn't work for this. It just throws weird errors.

Fixing the issue of styles not getting applied in global-error.tsx:

Skip this if you don't have any CSS modules imported in global-error.tsx. But if you do, for example:

import Styles from '@/styles/error.module.scss'

The cleanest and eaiest way is to use global styles instead of importing a module.

For example, you may have a global styles file in app called globals.scss and you are already importing it into layout.tsx:

import './globals.scss'

You can create unique class names in this file with a globally unique prefix like ErrorPage__ then use them in global-error.tsx.

The other hacky way if you want to import a module in global-error.tsx is to add a dummy class in the module:

// A dummy element to force load the styles in layout.tsx
.dummy {
  display: none;
}

And also import the module into layout.tsx:

import ErrorStyles from '@/styles/error.module.scss'

And use it in a div somewhere inside the body tag (it doesn't matter where) in the layout.tsx file:

<div className={ErrorStyles.dummy} />

Testing:

The following code is for testing the global error page. It should not be deployed in an actual production deployment.

In order to test this, a component should be dynamically rendered at runtime. Any statically generated component will be pre-built at build time and therefore fail the build if an error is thrown.

One easy way I managed to test this is in a client component that conditionally adds something:

{condition && <Component />}

In the client component file, I add a new function:

const fakeError = () => {
  console.log('Throwing a fake error!')
  throw new Error('Whoops!')
}

Then I use this as a className attribute value. (It doesn't matter what you use it as. I chose className because it didn't throw a type error.)

{condition && (
  <>
    <Component />
    <div className={fakeError()} />
  </>
)}

Now I build and run the production build, using npm or next:

npm run build && npm run start
# OR:
next build && next start

Navigate on localhost to the page which will fulfill the condition. And viola! (This is my custom error page. Your own style will definitely be different.)

image

Tip: The error object has some properties like name, message and digest which you can use to display the error details on the global-error page.

Good luck!

psirenny commented 6 months ago

This is very very bad DX.

ADTC commented 6 months ago

Apparently you can simulate it in dev by making a page route which has a client component which calls useRouter() and has a button that calls router.push to another route which is a server component that calls redirect from next/navigation to a third route. For some reason Next.js doesn't like this and throws a "Rendered more hooks than during the previous render" error. This triggers the global-error page where the error could be displayed.

Antonio-Laguna commented 1 month ago

I have created this component. Include it on any page:

'use client';

import { useState, useEffect } from 'react';

export const BrokenModule = (props: unknown) => {
    const [isBroken, setIsBroken] = useState(false);

    useEffect(() => {
        setTimeout(() => {
            setIsBroken(true);
        }, 2000);
    }, []);

    if (isBroken) {
        const test = props.test.not;
        return <p>{test}</p>;
    }

    return <p>Broken module</p>;
}

Guaranteed to get an error after 2 seconds.

ADTC commented 1 month ago

@Antonio-Laguna unfortunately your method can only test error.tsx but not global-error.tsx. I noticed this because I have different contents for each of these files.

As of now, to test the global-error.js | tsx file, the best known method is to do what I did and do a production build:

const fakeError = () => {
  console.log('Throwing a fake error!')
  throw new Error('Whoops!')
}

export default function FakeError({ condition }: { condition: boolean }) {
  return condition && <div className={fakeError()} />
}

Somewhere in your code, like layout.tsx:

<FakeError condition={true} />

As a reminder, you have to use build to create a production build and serve it. In NextJS projects, this is usually done with npm run serve. For any changes, you'll have to kill the server and rebuild a new production build.

mihaildono commented 3 weeks ago

I cant manage to catch the error... This is my app/error.tsx


import { useEffect } from 'react';

import { logException } from '@lib/utils';

export default function Error({
  error
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    console.error('error');
    logException(error);
  }, [error]);

  return (
    <div>
      <h2>Something went wrong!Error</h2>
    </div>
  );
}

This is my app/global-error.tsx


import { useEffect } from 'react';

import { logException } from '@lib/utils';

export default function GlobalError({
  error
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    console.error('global error');
    logException(error);
  }, [error]);

  return (
    <html>
      <body>
        <h2>Something went wrong!Global</h2>
      </body>
    </html>
  );
}

Set this ENV NODE_ENV production in my docker file and start my server with docker - so it should be prod?

How I trigger the error in a client component

<Button
          onClick={() => {
            throw new Error('custom err');
          }}
        />

I see the error thrown in console from client component button, but not in the error components.

"next": "14.0.4" "react": "^18", This was the default I initialised the project with

keighl commented 3 weeks ago

How I was able to view my global-error.tsx

// app/page.tsx

// Don't let next cache a functioning index.html at build time
export const dynamic = "force-dynamic";

export default async function Homepage() { 
  // ...
}
// app/layout.tsx

const RootLayout = async ({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) => {

  if (process.env.NEXT_PHASE !== PHASE_PRODUCTION_BUILD) {
    // This will allow the build, but fail at runtime
    throw new Error('Oh nooo!');
  }

  return <html /> 
}
aryomuzakki commented 3 days ago

I got here even though I managed to simulate it before with React Developer Tools and I answered the question in here: https://stackoverflow.com/a/78671438/12479141

Here is my solution :

  1. install React Developer Tools
  2. open your browser Developer Tools, and open the new 'Component' tab
  3. select the component inside 'Error Boundary' component, and click this (!) icon gwwYDAHI