remix icon indicating copy to clipboard operation
remix copied to clipboard

Deferred loader routes crash remix server on refresh

Open cephalization opened this issue 3 years ago • 8 comments
trafficstars

What version of Remix are you using?

@remix-run/node:0.0.0-experimental-9b7f37c9a, @remix-run/react:0.0.0-experimental-9b7f37c9a

Steps to Reproduce

I am using node v18.12.0, also tried using node v16.2.0. Using WSL 2 on Windows 11.

https://github.com/cephalization/remix-house-stack/issues/9

  • Clone template npx create-remix@latest --template cephalization/remix-house-stack
  • npm install the root of the project
  • npm run dev
  • Express API and Remix Dev Server will launch
  • Navigate to localhost:3000
  • Two things will happen;
    • A non-deferred request to localhost:5001/healthz that completes successfully
    • A deferred request to localhost:5001/delayedhealth that completes after 5 seconds
  • Now make a change to UI code or refresh the page
  • The deferred request now fails and the dev server outputs Error: The render was aborted by the server without a reason.

Expected Behavior

Refreshing the page or live reloading code changes does not abort renders.

Actual Behavior

Refreshing the page or live reloading code changes aborts renders and generates error messages.

cephalization avatar Nov 02 '22 13:11 cephalization

Can you upgrade to 0.0.0-experimental-2f4891673 and give it a go?

jacob-ebey avatar Jan 12 '23 00:01 jacob-ebey

I cloned the repo and gave your reproduction steps a go without issue.

The error is still logged but does not bring down your server. This error occurs from within react when the streaming renderer is aborted (in this case by the connection being closed by the client) before all suspended trees resolves.

jacob-ebey avatar Jan 12 '23 00:01 jacob-ebey

Is this something that needs fixing in my client code? I am able to confirm that it no longer crashes the remix server but the streaming request never recovers after the first refresh despite getting 200 responses from the API serving the deferred data.

cephalization avatar Jan 12 '23 23:01 cephalization

I also see this happening in my app as well.

It seems if I just refresh a page using defer many times, I eventually get this error to pop up.

That makes sense, because the client disconnected.

This is what doesn't make sense:

  await new Promise((resolve) => setTimeout(resolve, 10000));

Adding this to a deffered promise. Every time, it says The render was aborted by the server without a reason

it's like React will not stream if more than 10s has passed even if the client is still waiting

zackify avatar Jan 25 '23 18:01 zackify

I can confirm this error from Chrome using Remix 1.11.1:

2react-dom.development.js:20662 Uncaught Error: The server did not finish this Suspense boundary: The render was aborted by the server without a reason.
    at updateDehydratedSuspenseComponent (react-dom.development.js:20662:17)
    at updateSuspenseComponent (react-dom.development.js:20362:16)
    at beginWork (react-dom.development.js:21624:14)
    at beginWork$1 (react-dom.development.js:27426:14)
    at performUnitOfWork (react-dom.development.js:26560:12)
    at workLoopConcurrent (react-dom.development.js:26543:5)
    at renderRootConcurrent (react-dom.development.js:26505:7)
    at performConcurrentWorkOnRoot (react-dom.development.js:25738:38)
    at workLoop (scheduler.development.js:266:34)
    at flushWork (scheduler.development.js:239:14)

And this is my render:

<Suspense
  fallback={<p>Loading portfolio...</p>}
>
  <Await
    resolve={deferredPortfolio}
    errorElement={
      <PortfolioError />
    }
  >
    <PortfolioContent />
  </Await>
</Suspense>

The promise from the loader takes a few seconds (no more than 10), so I'm not sure what's happening here :thinking:

Any help is really appreciated!

jdnichollsc avatar Jan 26 '23 20:01 jdnichollsc

Okay guys, I made a big mistake. Maybe I missed this in the docs.

Check the server.entry.tsx. Update the ABORT_DELAY to be longer.

If you still see it, the client can disconnect and cause it. Otherwise the thing you’re waiting on is past the abort delay

zackify avatar Jan 26 '23 21:01 zackify

@zackify thanks for your support, let me check! It would be awesome if we can change that delay from a specific route :thinking:

jdnichollsc avatar Jan 26 '23 22:01 jdnichollsc

I updated that delay to 20 seconds but I'm getting this other error now:

Error: The render was aborted by the server without a reason.
    at abortTask (/frontend/node_modules/react-dom/cjs/react-dom-server.node.development.js:6436:43)
    at /frontend/node_modules/react-dom/cjs/react-dom-server.node.development.js:7002:14
    at Set.forEach (<anonymous>)
    at abort (/frontend/node_modules/react-dom/cjs/react-dom-server.node.development.js:7001:20)
    at Timeout.abort [as _onTimeout] (/frontend/node_modules/react-dom/cjs/react-dom-server.node.development.js:7051:7)
    at listOnTimeout (node:internal/timers:564:17)
    at processTimers (node:internal/timers:507:7)

What do you think? :thinking:

jdnichollsc avatar Jan 26 '23 23:01 jdnichollsc

Hi, I'm commenting here since it's the closest related issue to mine. I'm also running into problems with defer(). My problem is that the page is not served until fully rendered.

I had a go at the Discord help channel (tl;dr => A basic <Suspense>/<Await> example won't work on Windows) and we figure it was due WSL. However I can confirm that this is also broken when running remix on directly on Windows (no WSL). Any help appreciated :) For those interested example below.

// THIS CODE WON'T RUN AS EXPECTED ON WINDOWS REGARDLESS OF WSL

import { defer } from "@remix-run/node";
import { Await, useLoaderData } from "@remix-run/react";
import { Suspense } from "react";

const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
const getData = async () => {
    await delay(2000);
    return "Hello World";
};

export async function loader() {
    return defer({ message: getData() });
}

const DeferredComponent = () => {
    const data = useLoaderData<typeof loader>();
    return (
        <div>
            This will always show.
            <Suspense fallback={<p>Loading hello world...</p>}>
                <Await
                    resolve={data.message}
                    errorElement={<p>Error loading message</p>}>
                    {message => <p>This is your message: {message}</p>}
                </Await>
            </Suspense>
        </div>
    );
};

export default DeferredComponent;

ebz-jdev avatar Apr 14 '23 22:04 ebz-jdev

Just coming to bump this.

I'm on Windows and have a really weird issue with Suspense that could be the same problems here:

While suspense will work if it's the only thing on the page... If I bring in my full homepage content in Suspense will eventually stop working all together.

None of the components brough in use suspense at all, they are just straight content modules.

I can't for the life of me figure out exactly what's going on or why though

TheRealFlyingCoder avatar Apr 28 '23 02:04 TheRealFlyingCoder

Imagine the embarrasment of figuring it out 30 minutes later:

In my case there was a DomNesting Validation error in the markup, I had a nested Anchor tag.

For some reason this breaks Suspense completely, if you do:

<div>
  This will always show.
  <Suspense fallback={<p>Loading hello world...</p>}>
    <Await
      resolve={data.message}
      errorElement={<p>Error loading message</p>}>
         {message => <p>This is your message: {message}</p>}
    </Await>
   </Suspense>
   <a href="#">
     <p>Some text</p>
     <a href="#">another bad nested link</a>
   </a>
</div>

I'm not sure where this sort of behaviour ends, but a validation error doesn't break React, it still renders normally... so any number of warnings could cause suspense to break.

In a clean repo with just @ebz-jdev 's example it works fine, once you add a nested anchor it breaks

TheRealFlyingCoder avatar Apr 28 '23 02:04 TheRealFlyingCoder

This was fixed in 1.19.0. If issues persist please open a new ticket.

jacob-ebey avatar Aug 07 '23 17:08 jacob-ebey