react: React 18 bug: Uncaught Error when renderToString on server and hydrateRoot on client for Suspense lazy component

Hello!

I get the uncaught error while hydration Suspense component, but except this error, looks like hydration working correctly - fallback changed to lazy component successfully.

Prerequisites

  • react@18.0.0-rc.2
  • renderToString on server and hydrateRoot on client
  • render Suspense + lazy component
  • load page first time

Reproduction

Forked sandbox from Upgrading to React 18 on the server - https://codesandbox.io/s/modest-cdn-we3fvr

Error

Uncaught Error: The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.
    at updateDehydratedSuspenseComponent (react-dom.development.js:21341:9)
    at updateSuspenseComponent (react-dom.development.js:21005:24)
    at beginWork (react-dom.development.js:22225:18)
    at beginWork$1 (react-dom.development.js:27022:18)
    at performUnitOfWork (react-dom.development.js:26220:16)
    at workLoopSync (react-dom.development.js:26134:9)
    at renderRootSync (react-dom.development.js:26103:11)
    at performConcurrentWorkOnRoot (react-dom.development.js:25419:78)
    at workLoop (scheduler.development.js:266:34)
    at flushWork (scheduler.development.js:239:14)

Component example

const LazyFallback = () => <div>Loading...</div>;
const LazyBlock = lazy(() => import('../components/Block'));

export const App = () => {
  return (
    <div>
      <Suspense fallback={<LazyFallback />}>
        <LazyBlock />
      </Suspense>
    </div>
  );
};

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 8
  • Comments: 17

Most upvoted comments

Same issue in Next.js

next@12.1.1-canary.15
react@rc
react-dom@rc

This is using renderToString which effectively “aborts” any async work that is done since it’s a synchronous API. That gets treated as a “rejection” on the server. The client then considers this to be a “recoverable error” from the server. Which it is because it was a deopt in that case.

This case doesn’t happen if React.lazy has loaded by the time you render. Like if the server is warmed up. Not sure why it’s so hard to repro in Codesandbox. It probably does some request that warms it up.

The solution is to switch to the streaming API on the server. That’s why it’s important to upgrade both the server and the client as part of the React 18 upgrade.

One thing we could probably due is log a console.log on the server if there were still pending boundaries when using renderToString.

The warning can suggest upgrading to the streaming api in that scenario.

Important step - this works only for first page load from the server

Hey @lveillard from Next.js https://github.com/vercel/next.js/issues/35564#issuecomment-1077347776

experimental.runtime: 'nodejs' to forces next.js enable streaming rendering to solve this.