---
url: /mobx-view-model/v9/react/ssr.md
---

# Server-Side Rendering

SSR is supported with **ViewModelStore** and predictable hydration on the client.\
The main requirement is to keep the initial render **identical** on server and client.

#### Next.js

`mobx-view-model` works in Next.js **only with `"use client"`**.\
`"use server"` does not work because Next.js forbids React hooks in server components.

## Basic SSR with `withViewModel()`

Use a shared `ViewModelStore` and pass the same payload on server and client.\
This guarantees identical HTML for hydration.

```tsx
import { ViewModelBase, ViewModelStoreBase } from "mobx-view-model";
import { ViewModelsProvider, withViewModel } from "mobx-view-model-react";

class PageVM extends ViewModelBase<{ count: number }> {}

const Page = withViewModel(
  PageVM,
  ({ model }) => <div>{`count ${model.payload.count}`}</div>,
);

export const renderPage = (count: number) => {
  const vmStore = new ViewModelStoreBase();
  return (
    <ViewModelsProvider value={vmStore}>
      <Page payload={{ count }} />
    </ViewModelsProvider>
  );
};
```

## Hydration with preloaded data

If you preload data on the server, pass the same payload to the client.

```tsx
import { ViewModelStoreBase } from "mobx-view-model";
import { ViewModelsProvider } from "mobx-view-model-react";
import { hydrateRoot } from "react-dom/client";

const vmStore = new ViewModelStoreBase();

hydrateRoot(
  document.getElementById("app")!,
  <ViewModelsProvider value={vmStore}>
    <Page payload={{ count: 1 }} />
  </ViewModelsProvider>,
);
```

## Async `mount()`

If your `mount()` is async, the initial render will show **fallback** both on server and client.\
This prevents hydration mismatches.

```tsx
class PageVM extends ViewModelBase {
  async mount() {
    await sleep(100);
    super.mount();
  }
}
```

:::: tip Keep SSR and CSR identical\
Use the same `payload` and `ViewModelStore` setup on server and client.\
Do not rely on side effects inside `mount()` for the first render.\
::::
