Recoil: [SSR] Recoil doesn't work server side with Next.js 13
The Gist
When using Recoil + Next.js 13, I’m encountering an error while pre-rendering the page server side. The app works as expected client side though so no issue there, just when Recoil is used during server side pre-render.
The Error
TypeError: batcher is not a function
at MutableSnapshot.batchUpdates [as _batch] (webpack-internal:///(sc_client)/./node_modules/recoil/cjs/index.js:3447:3)
at eval (webpack-internal:///(sc_client)/./node_modules/recoil/cjs/index.js:3981:12)
at eval (webpack-internal:///(sc_client)/./app/RecoilProvider.tsx:17:9)
at Snapshot.eval [as map] (webpack-internal:///(sc_client)/./node_modules/recoil/cjs/index.js:3749:7)
at freshSnapshot (webpack-internal:///(sc_client)/./node_modules/recoil/cjs/index.js:3939:45)
at initialStoreState (webpack-internal:///(sc_client)/./node_modules/recoil/cjs/index.js:4397:20)
at eval (webpack-internal:///(sc_client)/./node_modules/recoil/cjs/index.js:4548:187)
at useRefInitOnce (webpack-internal:///(sc_client)/./node_modules/recoil/cjs/index.js:4078:19)
at RecoilRoot_INTERNAL (webpack-internal:///(sc_client)/./node_modules/recoil/cjs/index.js:4548:19)
at renderWithHooks (node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:7621:16)
The Environment
- next: 13.0.0
- react: 18.2.0
- react-dom: 18.2.0
- recoil: 0.7.6
The Code
// app/layout.tsx
'use client';
import RecoilProvider from './RecoilProvider';
export default function RootLayout(
{ children }: { children: JSX.Element }
) {
return (
<html lang="en-us">
<head>
<title>Next 13 + Recoil Example</title>
</head>
<body>
<RecoilProvider locale="en-us">{children}</RecoilProvider>
</body>
</html>
);
}
// app/atoms/i18n.tsx
'use client';
import { atom } from 'recoil';
export const locale = atom({
key: 'locale',
default: 'en-us',
});
// app/page.tsx
'use client';
import { useRecoilValue } from 'recoil';
import * as i18n from './atoms/i18n';
export default function Home() {
const locale = useRecoilValue(i18n.locale);
return (
<main>
<p>The locale: {locale}</p>
</main>
);
}
// app/RecoilProvider.tsx
'use client';
import { useCallback } from 'react';
import { RecoilRoot, SetRecoilState } from 'recoil';
import * as i18n from './atoms/i18n';
export default function RecoilProvider({
locale,
children,
}: {
locale: string,
children: JSX.Element,
}) {
const initializeState = useCallback(({ set }: { set: SetRecoilState }) => {
set(i18n.locale, locale);
}, [locale]);
return (
<RecoilRoot initializeState={initializeState}>
{children}
</RecoilRoot>
)
}
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 11
- Comments: 23 (1 by maintainers)
Commits related to this issue
- Adding a fallback for when unstable_batchedUpdates is undefined during SSR (#2086) Summary: Resolves facebookexperimental/Recoil#2082 Pull Request resolved: https://github.com/facebookexperimental/R... — committed to Gumichocopengin8/Recoil by leonbe02 2 years ago
- Adding a fallback for when unstable_batchedUpdates is undefined during SSR (#2086) Summary: Resolves facebookexperimental/Recoil#2082 Pull Request resolved: https://github.com/facebookexperimental/R... — committed to snipershooter0701/Recoil by leonbe02 2 years ago
- refactor: remove recoil The issue due to https://github.com/facebookexperimental/Recoil/issues/2082 — committed to hyochan/github-stats by hyochan a year ago
- ✨ feat: RecoilRootWrapper https://github.com/facebookexperimental/Recoil/issues/2082#issuecomment-1550624704 recoil과 ssr 충돌 해결 — committed to SWM-SMART/watchboard-front by junhea a year ago
This is not an issue. The way that Next.js works is server-side first. You can’t just wrap the app in the
<RecoilRoot>
You do have to have use the follow"use client"
however you have to put it in a wrapper component if you are using the app directory. According to Next.js state including state management must be handled on the client as there is no way to have state on the server side. Below is a working example that works for melayout.tsx file should look like this:
By doing this we put the Recoil provider on the client (browser) and anything that is not marked with “use client” will be rendered as server side. You can not add “use client” to the layout.tsx or layout.js, page.tsx or page.js files
then why should we use Nextjs if we shall end up turning everything into a client component?
@drarmstr Any estimate on when this change will be released in a new NPM version?
Has this project died or something? There hasn’t been a new version released since October of last year. I know Facebook had layoffs but I didn’t realize they laid off the entire team behind Recoil…
@MattSteedman Nope even if you put it as a wrapper any child components will still be server unless you use “use client” in each component. This is not a hack or workaround. Recoil is state management tools and anything that use state must be rendered on the client.
@drarmstr would you be able to help make a release that includes this fix? Still blocked by this. Or is there someone else who can help? Thank you!
Although this might work as a hack there are some files or compomponents you want server-side so wont this wrapper essentially put all those {children} in the clientside in your layout.tsx file