Mobx Server Side Rendering with Next.js
Tags: react, mobx, nextjs, javascript
Published: 2020-12-07 12:26:00

In this article, we are going to use Mobx with the root store pattern and Next.js framework to do a server-side rendering of pages. If you haven't, please check out my article about [Mobx root store pattern](https://dev.to/ivandotv/mobx-root-store-pattern-with-react-hooks-318d) with React hooks.

## The Project

The project is a simple counter that will start counting from 0. If a query parameter is passed in with a key of `start`, the server will use that query value and `hydrate` the counter to start counting from that value. Check it out [here](https://counter-demo.vercel.app/)

## Implementation

There are a couple of things that you need to watch out for when dealing with isomorphic applications. You must watch out for memory leaks, and you must take care not to mix up different users' data which can happen if you don't properly clean up your code after each server request. And there is also the process of `hydration`, you need to make sure to render the same context on the server and in the browser, when the page first loads or React will scream at you 😱.

## Memory Leaks

Because of the way how Mobx handles dependency tracking, it can leak memory when running on the server. Luckily, Mobx has solved that issue a long time ago, and all you have to do is enable `static rendering` functionality for Mobx.

```ts
import { enableStaticRendering } from "mobx-react-lite";
// there is no window object on the server
enableStaticRendering(typeof window === "undefined");
```
In the previous example, we have used `enableStaticRendering` function of the `mobx-react-lite` ( a special package that enables Mobx to be used with React) to enable static rendering whenever the `window` object is `undefined`, and since the `window` object only exists in the browser we enable static rendering only on the server.

```ts
// on the server
enableStaticRendering(true);

// in the browser
enableStaticRendering(false);
``` 
And that all you have to do to make Mobx work on the server.

## Always Fresh State

The second problem of potentially mixing the state of different requests can be solved by always creating a new Mobx store for each request (on the server) and when running in the browser, we create the store only once on the first load.

```ts
// file: src/providers/RootStoreProvider.tsx

// local module level variable - holds singleton store
let store: RootStore;

// function to initialize the store
function initializeStore():RootStore {
  const _store = store ?? new RootStore();

  // For server side rendering always create a new store
  if (typeof window === "undefined") return _store;

  // Create the store once in the client
  if (!store) store = _store;

  return _store;
}

```
Function `initializeStore()` will be used by the React provider component to create the store and use it as a value:

```ts
export function RootStoreProvider({
  children,
}: {
  children: ReactNode;
}) {
  // create the store
  const store = initializeStore();

  return (
    <StoreContext.Provider value={store}>{children}</StoreContext.Provider>
  );
}
```
And that is all it takes to handle creating Mobx stores both on the server and in the browser.

## Hydration 

In order to show the initial HTML content on the page (before React actually runs) we need to render it on the server-side, and then use the same data from the server-side to render it on the client and make the application "alive". That process is called `hydration`.

Next.js framework solves the problem of hydration for React apps, and all it's left for us to do is to use that process with our Mobx stores:

First, we need to have a special method on our root store that we will call with the hydration data. Root store will then distribute that hydration data to all other stores.

```ts
export type RootStoreHydration = {
  childStoreOne?: CounterHydration;
};

export class RootStore {
  hydrate(data: RootStoreHydration) {
    // check if there is data for this particular store
    if(data.childStoreOne){
      this.childStoreOne.hydrate(data.childStoreOne);
    }
  }
}
```
In the previous example, we have created the `hydrate` method on our root store that if there is hydration data it will get distributed to the child stores (which also have the `hydrate` method). Hydration data is a simple JSON serializable object with keys that map to child stores.

Now we need to change the `initializeStore` to accept the hydration data to be used when the root store is created.

```ts
function initializeStore(initialData?: RootStoreHydration): RootStore {
  const _store = store ?? new RootStore();

  // if there is data call the root store hydration method
  if (initialData) {
    _store.hydrate(initialData);
  }
  // For server side rendering always create a new store
  if (typeof window === "undefined") return _store;

  // Create the store once in the client
  if (!store) store = _store;

  return _store;
}
```

The reason that the `initialData` parameter is optional is that when navigating to different pages, some pages might have no data to hydrate the store, so `undefined` will be passed in.

Next, we need to change the `RootStoreProvider` component to accept hydration data.

```ts
function RootStoreProvider({
  children,
  hydrationData,
}: {
  children: ReactNode;
  hydrationData?: RootStoreHydration;
}) {
  // pass the hydration data to the initialization function
  const store = initializeStore(hydrationData);

  return (
    <StoreContext.Provider value={store}>{children}</StoreContext.Provider>
  );
}
```

And finally, we need to add `RootStoreProvider` component to the application and pass the hydration data from the Next.js framework itself.
Since we are planning to use the stores throughout the whole application (React tree), the best place to do that is as close to the React tree root as possible, and in the case of the Next.js framework that would be the [special `App`](https://nextjs.org/docs/advanced-features/custom-app) component. This `App` component is Next.js top-level component that is used to initialize all other pages.

```ts
function App({
  Component,
  pageProps,
}: {
  Component: NextPage;
  pageProps: any;
}) {
  return (
    <RootStoreProvider hydrationData={pageProps.hydrationData}>
      <Component {...pageProps} />;
    </RootStoreProvider>
  );
}
```
And that's it, everything is connected, and Mobx stores will correctly run both on the server and in the browser.

Please note that in this article we have used one root store to wrap the whole application, but you could also have any number of other root stores (or single stores) that could wrap only certain pages. The process is exactly the same but your `Provider` components will live somewhere else in the React component tree.

repository: https://github.com/ivandotv/mobx-nextjs-root-store

demo: https://counter-demo.vercel.app