# Next.js store setup

Use a store factory and a React context provider when you need request-isolated state in Next.js. Initialize the server and client stores with the same data so their first render matches. This example renders `Count: 10`; the buttons increment and decrement the count in the mounted client component.

Unlike the bound hook returned by [`create`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#create), a vanilla store exists independently of React. Create it with [`createStore`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#createstore), then connect client components with [`useStore`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#usestore). Context supplies the store for this part of the component tree.

## 1. Create a store factory

In your existing Next.js TypeScript project, install [Zustand](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand):

```bash
npm install zustand
```

Place the three shared files below beside your route's page file (`src/app/page.tsx` or `src/pages/index.tsx`). Export a factory rather than a store instance. Each call returns a new store containing the initial count and its actions. `CounterState` and `CounterActions` describe your application data and operations; their intersection gives the store both.

```ts title="counter-store.ts"
import { createStore } from 'zustand/vanilla'

export type CounterState = {
  count: number
}

export type CounterActions = {
  decrementCount: () => void
  incrementCount: () => void
}

export type CounterStore = CounterState & CounterActions

export const defaultInitState: CounterState = { count: 0 }

export const createCounterStore = (
  initState: CounterState = defaultInitState,
) =>
  createStore<CounterStore>()((set) => ({
    ...initState,
    decrementCount: () => set((state) => ({ count: state.count - 1 })),
    incrementCount: () => set((state) => ({ count: state.count + 1 })),
  }))
```

## 2. Provide a stable store to client components

Use a lazy `useState` initializer to retain the provided store across re-renders. The context holds the store API, not a bound hook. Derive its type from the factory's return value.

The custom hook returns the selector's result and subscribes the component to it. It throws a descriptive error if you use it outside the provider. `CounterExample` composes the provider with the client counter defined below, rendering the count and both buttons when you mount `<CounterExample />`.

```tsx title="counter-store-provider.tsx"
'use client'

import { createContext, useContext, useState, type ReactNode } from 'react'
import { useStore } from 'zustand'
import { Counter } from './counter'
import {
  createCounterStore,
  type CounterState,
  type CounterStore,
} from './counter-store'

type CounterStoreApi = ReturnType<typeof createCounterStore>

const CounterStoreContext = createContext<CounterStoreApi | undefined>(undefined)

type CounterStoreProviderProps = {
  children: ReactNode
  initialState: CounterState
}

export function CounterStoreProvider({
  children,
  initialState,
}: CounterStoreProviderProps) {
  const [store] = useState(() => createCounterStore(initialState))

  return (
    <CounterStoreContext.Provider value={store}>
      {children}
    </CounterStoreContext.Provider>
  )
}

export function useCounterStore<T>(selector: (state: CounterStore) => T): T {
  const store = useContext(CounterStoreContext)
  if (!store) {
    throw new Error('useCounterStore must be used within CounterStoreProvider')
  }
  return useStore(store, selector)
}

export default function CounterExample() {
  return (
    <CounterStoreProvider initialState={{ count: 10 }}>
      <Counter />
    </CounterStoreProvider>
  )
}
```

![The counter displays Count: 10 with Increment Count and Decrement Count buttons.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/d786cdcb323f62f900b667e4302d2361.png)

Create a client component that selects the count and each action separately. After hydration, clicking **Increment Count** adds one to the displayed count; **Decrement Count** subtracts one.

```tsx title="counter.tsx"
'use client'

import { useCounterStore } from './counter-store-provider'

export function Counter() {
  const count = useCounterStore((state) => state.count)
  const incrementCount = useCounterStore((state) => state.incrementCount)
  const decrementCount = useCounterStore((state) => state.decrementCount)

  return (
    <div>
      <p>Count: {count}</p>
      <button type="button" onClick={incrementCount}>Increment Count</button>
      <button type="button" onClick={decrementCount}>Decrement Count</button>
    </div>
  )
}
```

## 3. Supply matching initial state

Choose the example for your router. Both mount the provider at page level, following the per-route setup. The initial count is explicit application data, not a value computed independently in the browser.

### App Router

Keep your existing root layout and add this page. The Server Component passes only initial data to the client provider; it does not read or write a Zustand store.

```tsx title="src/app/page.tsx"
import { Counter } from './counter'
import { CounterStoreProvider } from './counter-store-provider'
import type { CounterState } from './counter-store'

export default function Home() {
  const initialState: CounterState = { count: 10 }

  return (
    <CounterStoreProvider initialState={initialState}>
      <Counter />
    </CounterStoreProvider>
  )
}
```

### Pages Router

Pass initial data through page props. Here, `getServerSideProps` supplies the initial count; the page passes that same value to the provider for server rendering and client hydration. Use this page-level provider without an additional counter provider in `_app.tsx`.

```tsx title="src/pages/index.tsx"
import { Counter } from './counter'
import { CounterStoreProvider } from './counter-store-provider'
import type { CounterState } from './counter-store'

type HomeProps = {
  initialState: CounterState
}

export const getServerSideProps = async (): Promise<{ props: HomeProps }> => {
  return { props: { initialState: { count: 10 } } }
}

export default function Home({ initialState }: HomeProps) {
  return (
    <CounterStoreProvider initialState={initialState}>
      <Counter />
    </CounterStoreProvider>
  )
}
```

Open the home page in your running Next.js app. It renders `Count: 10` with both buttons, then updates the displayed count when you click them.

## Initialization and scope

`createStore` takes a state creator, not Next.js-specific configuration options. In this example, these are your factory and provider inputs:

| Input | Type | Default | What it does |
| --- | --- | --- | --- |
| Factory `initState` | `CounterState` | `{ count: 0 }` | Supplies the count when creating a store. |
| Provider `initialState` | `CounterState` | Required | Supplies data to the factory on provider initialization. |

Keep the provided store stable: `initialState` initializes it, rather than synchronizing it on every render. Do not recreate the store in the provider's render body.

Place the provider at page level when you need a per-route store. If you do not need per-route scope, the setup also supports a provider in the App Router's layout or the Pages Router's `_app.tsx`. See [Store scope and lifecycle](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/store-scope-and-lifecycle) for choosing the store's lifetime.

## Pitfalls

- **Do not export a server-side singleton store.** A Next.js server handles multiple requests, so a global store can share state between them. Export the factory and create the store inside the provider instead.
- **Match the initial data.** Different server and client output causes hydration errors. Pass the same initial data to both stores; do not independently calculate a time-dependent or browser-only value during initialization.
- **Keep Server Components out of the store.** Do not read or write Zustand state from React Server Components. Pass initial data into the client provider and access the store through client components.

`useStore` uses `getInitialState()` for its server snapshot and `getState()` for its current client snapshot. Matching the two stores' initialization is therefore part of making their first render agree.

## Related

- [Initialize scoped stores](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/initialize-scoped-stores) — initialize a vanilla store from component props.
- [Selectors and subscriptions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/selectors-and-subscriptions) — choose what each component subscribes to.
- [Control persistence hydration](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/control-persistence-hydration) — coordinate loading persisted state with rendering.
