Skip to content

Suspended component during server rendering blocks request instead of using fallback. v12.1.2+ #36526

@flybayer

Description

@flybayer

Verify canary release

  • I verified that the issue exists in Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 21.2.0: Sun Nov 28 20:28:41 PST 2021; root:xnu-8019.61.5~1/RELEASE_ARM64_T6000
Binaries:
  Node: 16.14.2
  npm: 8.1.4
  Yarn: 1.22.1
  pnpm: 6.32.4
Relevant packages:
  next: 12.1.6-canary.12
  react: 18.1.0
  react-dom: 18.1.0

Describe the Bug

Prior to Next.js v12.1.2, throwing a promise during server rendering that never resolves resulted in the suspense fallback being rendered on the server. But now that results in the request never completing and next build never resolving.

All users of react-query + suspense, including all Blitz.js based apps, rely on that previous behavior. In the case of react-query, you usually want client-side data fetching, so you need the suspense fallback loading screen to be rendered on the server. Then on the client, the suspense fallback will continue to render until the data is loaded client side.

This is the PR that changed this: #35490

Expected Behavior

I'm not sure if this new behavior is a bug or is intended with the new streaming rendering things.

Either way, it's critical for us to have a way to opt into the previous behavior. One idea is a special flag to set on the thrown promise that would tell next.js/react to immediately render the fallback.

Example

const promise = new Promise(()=>{})
promise.reactForceFallback = true
throw promise

To Reproduce

Demo with latest nextjs version. The page never renders because it's stuck in server rendering.
https://codesandbox.io/s/divine-glitter-n07pnb?file=/pages/index.js

import { Suspense } from "react";

function Thing() {
  if (typeof window === "undefined") {
    throw new Promise(() => {});
  }
  return "Thing";
}

export default function IndexPage() {
  return (
    <div>
      <p>Next.js Example</p>
      <Suspense fallback="Loading...">
        <Thing />
      </Suspense>
    </div>
  );
}

Update: Throwing an error on the server instead of a promise causes the fallback to be rendered correctly, but then we get a flash of unstyled content on the client

import { Suspense } from "react";

function Thing() {
  if (typeof window === "undefined") {
    const e = new Error()
    e.name = "Rendering Suspense fallback..."
    delete e.stack
    throw e
  }
  return "Thing";
}

export default function IndexPage() {
  return (
    <div>
      <p>Next.js Example</p>
      <Suspense fallback="Loading...">
        <Thing />
      </Suspense>
    </div>
  );
}

If you downgrade nextjs to 12.1.1, then you'll see the loading indicator flash before the "Thing" text is rendered.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIssue was opened via the bug report template.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions