22
33> ** ⚠️ THIS PROJECT IS IN EARLY DEVELOPMENT AND IS NOT STABLE YET ⚠️**
44
5- This is minimal compatibility layer for effector + Next.js - it only provides one special ` EffectorNext ` provider component, which allows to fully leverage effector's Fork API, while handling some * special * parts of Next.js SSR and SSG flow.
5+ This is minimal compatibility layer for effector + Next.js - it only provides one special ` EffectorNext ` provider component, which allows to fully leverage effector's Fork API, while handling some _ special _ parts of Next.js SSR and SSG flow.
66
77So far there are no plans to extend the API, e.g., towards better DX - there are already packages like [ ` nextjs-effector ` ] ( https://github.com/risenforces/nextjs-effector ) .
88This package aims only at technical nuances.
@@ -45,7 +45,7 @@ Sid's are added automatically via either built-in babel plugin or our experiment
4545Add provider to the ` pages/_app.tsx ` and provide it with server-side ` values `
4646
4747``` tsx
48- import { EffectorNext } from " @effector/next"
48+ import { EffectorNext } from " @effector/next" ;
4949
5050export default function App({ Component , pageProps }: AppProps ) {
5151 return (
@@ -67,9 +67,9 @@ Notice, that `EffectorNext` should get serialized scope values via props.
6767Start your computations in server handlers using Fork API
6868
6969``` ts
70- import { fork , allSettled , serialize } from " effector"
70+ import { fork , allSettled , serialize } from " effector" ;
7171
72- import { pageStarted } from " ../src/my-page-model"
72+ import { pageStarted } from " ../src/my-page-model" ;
7373
7474export async function getStaticProps() {
7575 const scope = fork ();
@@ -82,13 +82,56 @@ export async function getStaticProps() {
8282 values: serialize (scope ),
8383 },
8484 };
85- };
85+ }
8686```
8787
8888Notice, that serialized scope values are provided via the same page prop, which is used in the ` _app ` for values in ` EffectorNext ` .
8989
9090You're all set. Just use effector's units anywhere in components code via ` useUnit ` from ` effector-react ` .
9191
92+ ### Dev-Tools integration
93+
94+ Most of ` effector ` dev-tools options require direct access to the ` scope ` of the app.
95+ At the client you can get current scope via ` getClientScope ` function, which will return ` Scope ` in the browser and ` null ` at the server.
96+
97+ Example of ` @effector/redux-devtools-adapter ` integration
98+
99+ ``` tsx
100+ import type { AppProps } from " next/app" ;
101+ import { EffectorNext , getClientScope } from " @effector/next" ;
102+ import { attachReduxDevTools } from " @effector/redux-devtools-adapter" ;
103+
104+ const clientScope = getClientScope ();
105+
106+ if (clientScope ) {
107+ /**
108+ * Notice, that we need to check for the client scope first
109+ *
110+ * It will be `null` at the server
111+ */
112+ attachReduxDevTools ({
113+ scope: clientScope ,
114+ name: " playground-app" ,
115+ trace: true ,
116+ });
117+ }
118+
119+ function App({
120+ Component ,
121+ pageProps ,
122+ }: AppProps <{ values: Record <string , unknown > }>) {
123+ const { values } = pageProps ;
124+
125+ return (
126+ <EffectorNext values = { values } >
127+ <Component />
128+ </EffectorNext >
129+ );
130+ }
131+
132+ export default App ;
133+ ```
134+
92135## Important caveats
93136
94137There are a few special nuances of Next.js behaviour, that you need to consider.
@@ -102,25 +145,25 @@ Normally in typical SSR application you could use it to calculate some server-on
102145``` tsx
103146// typical custom ssr example
104147// some-module.ts
105- export const $serverOnlyValue = createStore (null , { serialize: " ignore" })
148+ export const $serverOnlyValue = createStore (null , { serialize: " ignore" });
106149
107150// request handler
108151
109152export async function renderApp(req ) {
110- const scope = fork ()
111-
112- await allSettled (appStarted , { scope , params: req })
113-
114- // serialization boundaries
115- const appContent = renderToString (
116- // scope object can be used for the render directly
117- <Provider value = { scope } >
118- <App />
119- </Provider >
120- )
121- const stateScript = ` <script>self.__STATE__ = ${serialize (scope )}</script> ` // does not contain value of `$serverOnlyValue`
122-
123- return htmlResponse (appContent , stateScript )
153+ const scope = fork ();
154+
155+ await allSettled (appStarted , { scope , params: req });
156+
157+ // serialization boundaries
158+ const appContent = renderToString (
159+ // scope object can be used for the render directly
160+ <Provider value = { scope } >
161+ <App />
162+ </Provider >
163+ );
164+ const stateScript = ` <script>self.__STATE__ = ${serialize (scope )}</script> ` ; // does not contain value of `$serverOnlyValue`
165+
166+ return htmlResponse (appContent , stateScript );
124167}
125168```
126169
@@ -140,7 +183,7 @@ export const $serverOnlyValue = createStore(null, { serialize: "ignore" })
140183
141184export function Component() {
142185 const value = useUnit ($serverOnlyValue )
143-
186+
144187 return value ? <>{ value } <> : <>No value</>
145188}
146189
@@ -149,7 +192,7 @@ export async function getServerSideProps(req) {
149192 const scope = fork ()
150193
151194 await allSettled (appStarted , { scope , params: req })
152-
195+
153196 // scope.getState($serverOnlyValue) is not null at this point
154197
155198 return {
@@ -169,16 +212,15 @@ You can use custom serialization config instead
169212```ts
170213const $date = createStore<null | Date >(null, {
171214 serialize : {
172- write : dateOrNull => (dateOrNull ? dateOrNull .toISOString () : dateOrNull ),
173- read : isoStringOrNull =>
215+ write : ( dateOrNull ) => (dateOrNull ? dateOrNull .toISOString () : dateOrNull ),
216+ read : ( isoStringOrNull ) =>
174217 isoStringOrNull ? new Date (isoStringOrNull ) : isoStringOrNull ,
175218 },
176- } )
219+ } );
177220```
178221
179222[Docs](https://effector.dev/docs/api/effector/createStore#example-with-custom-serialize-configuration)
180223
181-
182224### ESM dependencies and library duplicates in the bundle
183225
184226Since Next.js 12 [ESM imports are prioritized over CommonJS imports](https://nextjs.org/blog/next-12#es-modules-support-and-url-imports). While CJS-only dependencies are still supported, it is not recommended to use them.
@@ -191,7 +233,6 @@ You can also check it manually via `Debug -> Sources -> Webpack -> _N_E -> node_
191233
192234<img width = " 418" alt = " image" src = " https://user-images.githubusercontent.com/32790736/233786487-304cfac0-3686-460b-b2f9-9fb0de38a4dc.png" >
193235
194-
195236## ⚠️ App directory (Next.js Beta) ⚠️
196237
197238#### 0. Make sure you aware of current status of the App directory
@@ -216,18 +257,17 @@ To do so, create `effector-provider.tsx` file at the top level of your `app` dir
216257
217258```tsx
218259// app/effector-provider.tsx
219- ' use client' ;
260+ " use client" ;
220261
221- import type { ComponentProps } from ' react' ;
222- import { EffectorNext } from ' @effector/next' ;
262+ import type { ComponentProps } from " react" ;
263+ import { EffectorNext } from " @effector/next" ;
223264
224265export function EffectorAppNext({
225266 values ,
226267 children ,
227268} : ComponentProps<typeof EffectorNext >) {
228269 return <EffectorNext values = { values } >{ children } </EffectorNext >;
229270}
230-
231271```
232272
233273You should use this version of provider in the `app` directory from now on.
@@ -242,18 +282,16 @@ If you are using [multiple Root Layouts](https://beta.nextjs.org/docs/routing/de
242282
243283```tsx
244284// app/layout.tsx
245- import { EffectorAppNext } from "project-root/app/effector-provider"
285+ import { EffectorAppNext } from "project-root/app/effector-provider";
246286
247287export function RootLayout({ children } : { children : React .ReactNode } ) {
248288 return (
249289 <html lang = " en" >
250290 <body >
251- <EffectorAppNext >
252- { /* rest of the components tree */ }
253- </EffectorAppNext >
291+ <EffectorAppNext >{ /* rest of the components tree */ } </EffectorAppNext >
254292 </body >
255- </html >
256- )
293+ </html >
294+ );
257295}
258296```
259297
@@ -265,7 +303,7 @@ In this case you will need to add the `EffectorAppNext` provider to the tree of
265303
266304```tsx
267305// app/some-path/page.tsx
268- import { EffectorAppNext } from "project-root/app/effector-provider"
306+ import { EffectorAppNext } from "project-root/app/effector-provider";
269307
270308export default async function Page() {
271309 const scope = fork ();
@@ -278,9 +316,10 @@ export default async function Page() {
278316 <EffectorAppNext values = { values } >
279317 { /* rest of the components tree */ }
280318 </EffectorAppNext >
281- )
319+ );
282320}
283321```
322+
284323This will automatically render this subtree with effector's state and also will automatically "hydrate" client scope with new values.
285324
286325You're all set. Just use effector's units anywhere in components code via `useUnit` from `effector-react`.
0 commit comments