next.js
next.js copied to clipboard
next build times out with recoil.js async selectors using suspense
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).

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

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.
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.
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.
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:

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.
+1 on this issue, async selector times out on next build.
+1
+1
+1
+1
+1
+1
+1
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.