vercel / next.js

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

Non-NextJS Error Boundary doesn't catch errors originating from server components. #58754

Open jviall opened 10 months ago

jviall commented 10 months ago

Link to the code that reproduces this issue

https://codesandbox.io/p/devbox/nextjs-error-boundary-catches-server-throws-mmvlsd

To Reproduce

Current vs. Expected behavior

Current Behavior: any custom <ErrorBoundary /> component will not catch an error that is thrown from a descendent server component, instead it will be uncaught, unless a error.tsx file is defined somewhere for the given route.

Expected Behavior: <ErrorBoundary />-like components should catch server errors without needing to be implemented as Route Segment error.tsx files.

Verify canary release

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP PREEMPT_DYNAMIC Sun Aug  6 20:05:33 UTC 2023
Binaries:
  Node: 20.9.0
  npm: 10.1.0
  Yarn: 1.22.19
  pnpm: N/A
Relevant Packages:
  next: 14.0.4-canary.9
  eslint-config-next: 14.0.2
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.2.2
Next.js Config:
  output: N/A

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

App Router

Additional context

In the linked sandbox, adding "use client"; to the <BadComponent /> resolves the issue, proving the gap in functionality between Next's proprietary error boundary and how "native" error boundaries behave. I also don't believe this discrepancy has always been the case.

jviall commented 10 months ago

The Legacy react docs mention that Server Rendering errors aren't caught by error boundaries, but this note was removed in the new React docs--has this changed?

Note Error boundaries do not catch errors for: Event handlers (learn more) Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks) Server side rendering Errors thrown in the error boundary itself (rather than its children)

https://legacy.reactjs.org/docs/error-boundaries.html

jviall commented 10 months ago

This discussion topic seems to target the same issue https://github.com/vercel/next.js/discussions/53618

EvHaus commented 10 months ago

Using App Router -- currently it's possible to catch server-side errors like this:

import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

const ThisWillFail = async () => {
    // If this request fails I want the <ErrorBoundary> to catch it, rather than having
    // the whole page fails and fallback to `error.tsx`.
    const data = fetch('https://somepage.com/api');
    return <div>Hello</div>;
}

const Thing = () => (
    <ErrorBoundary fallback={<div>An error happened</div>}>
        <Suspense fallback={<div>Loading...</div>}>
            <ThisWillFail />
        </Suspense>
    </ErrorBoundary>
);

export default Thing;

However, if I want to display the actual error message to the user (ie. use the FallbackComponent prop instead of the fallback prop on <ErrorBoundary>) there are some problems. I want this:

import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

const ThisWillFail = async () => {
    // If this request fails I want the <ErrorBoundary> to catch it, rather than having
    // the whole page fails and fallback to `error.tsx`.
    const data = fetch('https://somepage.com/api');
    return <div>Hello</div>;
}

const ErrorHandler = ({error}) => {
    return <div>{error.message}</div>
}

const Thing = () => (
    <ErrorBoundary FallbackComponent={ErrorHandler}>
        <Suspense fallback={<div>Loading...</div>}>
            <ThisWillFail />
        </Suspense>
    </ErrorBoundary>
);

export default Thing;
  1. Firstly, adding FallbackComponent causes this failure in Next.js: Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server"..
  2. I can get around that error by putting use client at the top of the file. But that causes the whole app to go into an infinite rendering loop where (for some reason) the error is caught, but then the whole app re-renders, fails again, gets caught, gets re-rendered, etc...

The Next.js Error Handling documentation mentions using ErrorBoundary but doesn't provide any examples of how to do it.

Any help would be appreciated here.

exelimpichment commented 6 months ago
  1. I can get around that error by putting use client at the top of the file. But that causes the whole app to go into an infinite rendering loop where (for some reason) the error is caught, but then the whole app re-renders, fails again, gets caught, gets re-rendered, etc...

looks like no infinite loops for now. works form me fine the way you described

th91vi commented 3 months ago

@EvHaus thanks a lot for your answer. Your proposition is working for me on Next 14.1.4

Can you share more details on why ErrorBoundary and Suspense must be o the same component level to make it work?

Previously I was trying to use my ErrorBoundary 1 level below my Suspense and it was not working.

Don't know if I am an edge case, but it's very frustrating that Vercel provides poor documentation about how to handle RSC errors.

EvHaus commented 3 months ago

@th91vi Sorry, I don't know. That's why this issue exists. :) Need Next.js devs to help us.

zecka commented 3 months ago

@EvHaus Note that using <Suspense> here is probably not the desired behavior for most people looking to catch errors in server components.

Correct me if I'm wrong, but don't forget that suspense will shift part of the server rendering to a second time and display a fallback to the client in the meantime.