vercel / next.js

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

Custom error.tsx page is not rendered for dynamic routes using generateStaticParams / ISR in app router. Pages router 500.tsx is rendered instead #62046

Open bkrajewski94 opened 8 months ago

bkrajewski94 commented 8 months ago

Link to the code that reproduces this issue

https://github.com/bkrajewski94/next-isr-error-page-bug

To Reproduce

  1. Install dependencies with npm install
  2. Build the application with npm run build
  3. Start the application npm start
  4. Navigate to localhost:3000/test

Current vs. Expected behavior

If you use generateStaticParams inside a dynamic route like [slug]/page.tsx, none of the custom error.tsx/global-error.tsx pages will be displayed in case of an error. What you'll see instead is the NextJS inbuilt error page

image

In the below example I used generateStaticParams to implement ISR (on request static generation):

// src/app/[slug]/page.tsx

async function getData() {
  const res = await fetch("https://api.example.com/...");

  if (!res.ok) {
    // This will activate the closest `error.js` Error Boundary
    throw new Error("Failed to fetch data");
  }

  return res.json();
}

export default async function Page({ slug }: any) {
  const data = await getData();

  return (
    <main>
      <div>{slug}</div>
      <div>{JSON.stringify(data)}</div>
    </main>
  );
}

// error.tsx is not rendered when:
export function generateStaticParams() {
  return [];
}

// error.tsx is rendered correctly when instead of generateStaticParams I use:
// export const dynamic = "force-dynamic";

It only breaks if you build your application (works fine on devserver). As you can imagine it can be a big problem when you'd like to e.g. render a custom error page, or log error to an external system inside the error boundary.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.0.0: Fri Sep 15 14:41:43 PDT 2023; root:xnu-10002.1.13~1/RELEASE_ARM64_T6000
Binaries:
  Node: 20.10.0
  npm: 10.2.3
  Yarn: 1.22.21
  pnpm: 7.1.0
Relevant Packages:
  next: 14.1.1-canary.52 // Latest available version is detected (14.1.1-canary.52).
  eslint-config-next: 14.1.0
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.3.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

App Router

Which stage(s) are affected? (Select all that apply)

next build (local), next start (local), Other (Deployed)

Additional context

No response

marlonschlosshauer commented 8 months ago

I stumbled across this today, too, but for the 404 pages.

// src/app/[[...slug]]/page.tsx
export function generateStaticParams() {
  return ["home", "about", "career"].map((slug) => ({ slug: [slug] }));
}

export const dynamicParams = false;

export default function Page({ params }: any) {
  const { slug } = params;
  return <div>{slug}.</div>;
}
// src/app/[[...slug]]/not-found.tsx
export default function NotFound() {
  return <div>this page will never be rendered</div>;
}

Accessing /home etc. works but if you enter e.g /test you'll get the default Next 404 page, instead of the specific not-found.tsx for that route.

Repro: https://github.com/marlonschlosshauer/next-dynamic-routes-static-params-not-found

bkrajewski94 commented 8 months ago

Hi @marlonschlosshauer, I've managed to better identify the problem and I've found a workaround, although it's not the best one.

What I've found out is that when /app router dynamic route throws an error, for some reason the /pages router 500.js page is rendered instead of the /app routers error.js / global-error.js.

I guess it could be the same for app/not-found.js, so what you could do is you could add 404.js page inside the page router, and see if it works.

marlonschlosshauer commented 8 months ago

Hey @bkrajewski94 Thanks for the suggestion! Interestingly enough, that (thankfully?) didn't fix it for me. Running the app in dev mode also indicates that it is still using the global app directory 404 page:

 ✓ Compiled /[[...slug]] in 434ms (408 modules)
 ✓ Compiled /not-found in 84ms (413 modules)

Maybe we're having different issues after all. I work around for my issue would be to just use the default /not-found.js, although I'd like to avoid that to circumvent needing an additional, top-level, layout.

bkrajewski94 commented 8 months ago

I've played around with it a bit by implementing my own error boundary. I think that the issue is caused by componentDidCatch not being executed in case of ISR

coffeecupjapan commented 8 months ago

@marlonschlosshauer It seems like Next.js only recognizes 404 page in root directory and cannot recognize 404 page in dynamic directory.

https://github.com/vercel/next.js/blob/160bb99b06e9c049f88e25806fd995f07f4cc7e1/packages/next/src/build/index.ts#L923-L926

But it is For now so you can wait until core team to tackle with this issue. Code above is only a build phase problem, but there are also server side and client side problems and I cannot pick all dependencies of this change. Sorry.

You only can customize root directory 404 page for now. See doc.

sroebert commented 7 months ago

Having the same issue as well, with static routes and the app router. If I add a pages folder with 500.tsx, that error component is rendered instead.

Zvanderschyff commented 6 months ago

Having the same issues when running a Production build. Had to create a _error.js file in the page router in order to have a custom error show when there is a server side error. The documentation states: If an error is thrown inside a Server Component, Next.js will forward an Error object (stripped of sensitive error information in production) to the nearest error.js file as the error prop.

I don't know if that means that if you have an error.js in your app router that it is supposed to use that for server errors too or if it just means that it sends through an Error object and we need to somehow handle that.

Funny enough when using the error.js in App router; when there is a client side error it doesn't display the custom error but instead a blank page and in the console I have a "more hooks rendered than in the previous render" error. Had to write my own ErrorBoundary in order for client errors to work fine. I believe there is some setState in the ErrorBoundary of Next that is misbehaving.

Hopefully they find a fix soon.

11Extrano commented 3 months ago

+1 . Why don't internal errors jump to app/error.tsx? It seems that only errors thrown in server-side components will jump to app/error.tsx