next.js icon indicating copy to clipboard operation
next.js copied to clipboard

next build times out with recoil.js async selectors using suspense

Open pcreehan opened this issue 2 years ago • 4 comments

Verify canary release

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

Provide environment information

Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 10 Pro
Binaries:
  Node: 16.15.0
  npm: N/A
  Yarn: N/A
  pnpm: N/A
Relevant packages:
  next: 12.1.7-canary.27
  react: 18.1.0
  react-dom: 18.1.0

UPDATE (7/8/22): Confirmed issue still exists with 12.2.0

What browser are you using? (if relevant)

n/a

How are you deploying your application? (if relevant)

n/a

Describe the Bug

running next build times out with

warn  - Restarted static page generation for / because it took more than 60 seconds
warn  - See more info here https://nextjs.org/docs/messages/static-page-generation-timeout

in a very simple application when using recoil with an async selector and <React.Suspense> following an example very similar to this: https://recoiljs.org/docs/guides/asynchronous-data-queries#asynchronous-example .

Here's my example with console showing the build failure (it tries 3 times then errors out). image

Switching to using the Loadable strategy works, but is less ideal: image

next dev runs fine and the application 'works' using either method, it's only when next is generating the static pages that it has a problem.

I am assuming this is not a Recoil bug exclusively because I assume they would have seen a problem building in a different framework like react-scripts or whatever.

Expected Behavior

next build should be able to generate static pages using recoil async selectors surrounded in React.Suspense.

To Reproduce

npx create-next-app@latest --typescript

npm install recoil@latest

modify _app.tsx to

import "../styles/globals.css";
import type { AppProps } from "next/app";
import { RecoilRoot } from "recoil";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <RecoilRoot>
      <Component {...pageProps} />
    </RecoilRoot>
  );
}

export default MyApp;

Modify index.tsx to

import type { NextPage } from "next";
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { selector, useRecoilValue } from "recoil";
import React from "react";
interface IResponse {
  success: number;
}
const myAsyncSelector = selector({
  key: "try-this",
  get: async () => {
    let apiRes = await fetch(
      "https://mocki.io/v1/625d1778-9b02-4f65-8fa5-31e91191c18f"
    );
    return (await apiRes.json()) as IResponse;
  },
});
const MyComponentUsingAsyncSelector = () => {
  const apiresponse = useRecoilValue(myAsyncSelector);
  return <div>Success Value = {apiresponse.success}</div>;
};
const Home: NextPage = () => {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <React.Suspense fallback={<div>Loading...</div>}>
          <MyComponentUsingAsyncSelector />
        </React.Suspense>
      </main>
    </div>
  );
};

export default Home;

npm run build

observe the warnings and eventual failure of the build while generating static pages.

pcreehan avatar Jun 01 '22 16:06 pcreehan

I'm also experiencing this. This idea came to mind but the build step still hangs.

Also to note, using useRecoilValueLoadable does successfully next build. We just end up losing the Suspense feature and have to handle the three different states that the async value can be in.

karbica avatar Jun 02 '22 22:06 karbica

In next dev other than next build does Suspense work well in async recoil state ? I don't think it's just a problem with the next build

In the example code you uploaded, Loading... is not displayed on the screen even if you intentionally delay the selector part like await new Promise((resolve) => { setTimeout(() => { resolve(null); }, 3000); });

In addition, there are problems that do not respond when you refresh or enter a new tab.

This does not appear to work as intended by Suspense

I'm not saying that the Problem Reproduction Code is wrong. It is not only a problem that occurs in next build, but I think Suspense is not available in nextjs yet.

parkgang avatar Jul 26 '22 06:07 parkgang

I don't think the problem is that it's not available in nextjs yet. As I linked in my original post, the async example in Recoil's own documentation says that it does work with Suspense: image

Yes, everything seems to work ok in next dev as far as I can tell. The problem occurs during static page generation in next build.

pcreehan avatar Jul 29 '22 13:07 pcreehan

+1 on this issue, async selector times out on next build.

callensm avatar Aug 02 '22 13:08 callensm

+1

marviobezerra avatar Sep 25 '22 22:09 marviobezerra

+1

joohaem avatar Jan 10 '23 05:01 joohaem

+1

Tekiter avatar Jan 16 '23 16:01 Tekiter

+1

imfunniee avatar Feb 09 '23 16:02 imfunniee

+1

rZinnatov avatar Mar 17 '23 10:03 rZinnatov

+1

mihara0320 avatar Mar 27 '23 12:03 mihara0320

+1

Broniolael90 avatar Mar 28 '23 10:03 Broniolael90

Hey guys, been hitting the same issue. I've found a workaround though which fixes the build job. It's less than ideal as it eliminates any server-side data fetching but that's something I'm accepting for now. Lots of new tech at play here, looking forward to it all maturing.

Tl;dr: wrap the async recoil selector in a "safety" selector which returns early when server-rendering so that the async selector is not invoked.

export namespace Queries {
  // my original async selector
  export const myData = selector({
    key: 'myData.query',
    get: async () => fetch('/api/data')
      .then((res) => res.json() as Promise<{ results: any[] }>);
  });
  
  // wrap the original selector so on the server it returns early with empty data
  export const mySafeData = selector({
    key: 'myData.query.safe',
    get: ({ get }) => {
      if (typeof window === 'undefined') return { results: [] };
      return get(myData);
    },
  });
}

Hope that helps. Super curious if anyone has a better solution that I've not seen as this thread's quite old now.

tim-codes avatar Mar 29 '23 16:03 tim-codes