Explore Next.js state management with Zustand. Craft a sync hook for seamless server-client state coordination. Elevate performance and data consistency in your Next.js app.
What we will achieve?
The NextJS is a real game changer in the web ecosystem. However, integration with third-party libraries for state management like Zustand or Redux may be challenging. That's why today, we'll integrate Next.js app with Zustand, and at the end, you will know how to manage your state, keep it synced with the server and manage effectively.
Server/client state synchronization problem
Check the following code snippet which shows the difference in state returned from the server and the initial client state.
In console.log statements we have different values for counter variables from the server and client. This is a big problem, our application will be buggy as hell... So, we need to:
- read the state from the server on the client before hydration,
- replace the Zustand store state - make synchronization between server/client.
When you try to run this code, you'll get the following error from NextJS in a development environment.
Hydration refers to the process of taking a server-rendered React application and making it interactive on the client-side. NextJS combines server-side rendering (SSR) with client-side rendering (CSR) to improve the performance and user experience of web applications
Synchronizing state for server/client
We need to sync the state before the hydration. Our useCounterStore hook created by create function from Zustand, exposes static method to change the state - setState.
The state change done in Zustand happens immediately. It's not async operation - you may understand it as a simple variable change.
In addition, the components or other application layers are listening for state changes. It's a typical implementation of observable pattern. If you're reading state with useCounterStore hook - you're automatically subscribed and the component will rerender after the state change.
With that information, we may implement the following hook:
And then we'll use our hook as follows:
Okay, so let's describe what happened. First, we are passing to the useStoreSync hook, the useCounterStore original hook and the state from server.
Next, we're checking about synchronization status. If a sync has not happened yet, we're setting the initial state as the state passed from the server, and then we're setting the flag to false. Thanks to this we'll avoid multiple state changes and not needed rerenders.
The most important part is to call it at the first line of initial page component and we need to do it only once per store.
The showcase example
To show the result of integration we need some real-world examples instead of the counter one. Let's say we have two different views in which we want to display articles. The first one should fetch them only on the client, the second one should retrieve them on the server and sync up with our client state.
This should look like this:
Small app demo
Take a look at the view behavior when changing url. On the first tab when we change URLs we have Loading message.
In second tab, we display articles immediately (they are taken from the server) and synced up with the client as I mentioned before.
Integration with getStaticProps / getServerProps
Let's say we're using getStaticProps or getServerProps and we want to load the list of articles on the server. Then, we want to pass the list from the server and generate a page. The data loaded on the server must be synced with the client state. To achieve that we need the following code:
The same rule applies for getServerProps, just the function name will be different, so let's skip that.
Integration with server components/server actions
How to use it with server components in new NextJS API? We need a special, client-side only component that will use our hook and return null. Take a look at the following code and notice the usage of use client directive.
Now, let's use our component inside ArticlesPage:
Notice that we used the SyncedWithArticles component at the beginning of the JSX code - it triggers the sync procedure.
The final code
Here you have the hook implementation:
Under the following Codesandbox, you have the implementation discussed in this article.
If you are curious how you may use this hook and would like to have more examples, just check the following Dream stack for React developer repository.
Summary and conclusions
We integrated and synced up the state from the server with the client one. A small hook needs to be called at the beginning. Life is now much easier!
What is really cool, the same solution will work for other frameworks like Gatsby.
The most important part is that the implementation differs for "old" and "new" NextJS. With the previous getStaticProps and getServerProps we need just to call a hook at the beginning of the page.
However, for server components and server actions we need a special wrapper component that will be used only on client.
If you want to now more about Zustand and NextJS, feel free to check these articles: