# Set up Zustand in Next.js

Create a vanilla store per provider instance, expose it to client components, and mount the provider in either Next.js router without sharing a module-level store across server requests.

## When to use this

Use a provider-owned store when Next.js server rendering and client components need to share application state. A module-level store can be shared by simultaneous server requests, so create the store inside the provider instead.

## Create the store and provider

Install [`zustand`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand), which includes the vanilla entry point:

```bash
npm install zustand
```

Create the vanilla store with [`createStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#createstore) from [`zustand/vanilla`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-vanilla). The factory accepts initial count data and keeps the increment and decrement actions with that state.

```ts title="src/stores/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,
) => {
  return createStore<CounterStore>()((set) => ({
    ...initState,
    decrementCount: () => set((state) => ({ count: state.count - 1 })),
    incrementCount: () => set((state) => ({ count: state.count + 1 })),
  }))
}
```

The provider creates its store with a lazy state initializer, so a re-render of that provider keeps the same store instance. Its context uses [`StoreApi`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#storeapi) to retain the store's state type. The custom hook reads the context and subscribes through [`useStore`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#usestore); it throws a clear error if a component renders outside the provider.

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

import { createContext, useContext, useState, type ReactNode } from 'react'
import { useStore } from 'zustand'
import type { StoreApi } from 'zustand/vanilla'

import { createCounterStore } from '../stores/counter-store'

type CounterStore = {
  count: number
  decrementCount: () => void
  incrementCount: () => void
}

type CounterStoreApi = StoreApi<CounterStore>

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

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

  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)
}
```

Add a client component that selects the count and action it needs. It renders `Count: 0` initially; clicking **Increment Count** updates the displayed count, and **Decrement Count** lowers it.

```tsx title="src/components/counter.tsx"
'use client'

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

type CounterStore = {
  count: number
  decrementCount: () => void
  incrementCount: () => void
}

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

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

## Mount the provider

Choose the mounting point for your router. A provider around the whole application shares one store instance within that provider tree. Put it at a route instead when that route needs its own store instance; do not create a new provider per route unless route-scoped state is required.

### App Router

Render the client provider from the server root layout. The root layout stays a server component; the counter itself is a client component.

```tsx title="src/app/layout.tsx"
import type { ReactNode } from 'react'

import { CounterStoreProvider } from '../providers/counter-store-provider'

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <CounterStoreProvider>{children}</CounterStoreProvider>
      </body>
    </html>
  )
}
```

Render the counter from the route page:

```tsx title="src/app/page.tsx"
import { Counter } from '../components/counter'

export default function Page() {
  return <Counter />
}
```

The page displays the counter inside the provider, and client-side button clicks update its selected state.

### Pages Router

For route-scoped state, wrap the page content in the provider:

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

export default function Home() {
  return (
    <CounterStoreProvider>
      <Counter />
    </CounterStoreProvider>
  )
}
```

The page shows the same initial count and buttons; each click updates the count through its provider-owned store. For an app-wide store, wrap the page component in the provider from the Pages Router's custom application component.

## Initial data and request boundaries

The provider sample initializes its store with the factory's default `{ count: 0 }`. For route-specific state, place the provider at the route boundary.

| Factory input | Type | Default | Effect |
| --- | --- | --- | --- |
| `initState` | `CounterState` | `defaultInitState` (`{ count: 0 }`) | Supplies the starting count for the store instance. |

## Pitfalls

- Do not define one global store for the server to reuse across requests. Construct the store inside the provider so each provider instance owns its store.
- Do not read or write the store from a React Server Component. Keep store access in client components.
- Initialize server and client rendering with the same data; different initial output can cause hydration errors. See [Handle persisted-state hydration](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/handle-persisted-state-hydration) for the guide's hydration-specific task.
- For vanilla-store details on `set`, `get`, `setState`, and `getState`, see [Use a vanilla store in React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-a-vanilla-store-in-react).

## See also

- [React and vanilla stores](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-and-vanilla-stores)
- [Use a vanilla store in React](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/use-a-vanilla-store-in-react)
- [Create and bind a React store](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/create-and-bind-a-react-store)
- [Zustand's Next.js setup guide](https://zustand.docs.pmnd.rs/learn/guides/nextjs)
