# Control persisted hydration

Use `skipHydration` when the application must choose when persisted data enters the store, and use `onRehydrateStorage` to observe completion or report an error.

## When to use this

By default, `persist` hydrates the store during initialization. In an SSR application, or whenever the first render must use only the initial state, set `skipHydration: true` and start hydration after the appropriate client-side lifecycle point.

## Start hydration after the component mounts

Create the store with [`create`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#create) and wrap its initializer with [`persist`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand-middleware#persist). The callback returned by `onRehydrateStorage` runs after hydration: its second argument is defined when hydration fails.

```tsx
import { useEffect, useState } from 'react'
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

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

const useCounterStore = create<CounterStore>()(
  persist(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
    }),
    {
      name: 'counter-storage',
      skipHydration: true,
      onRehydrateStorage: () => {
        console.log('hydration started')

        return (_state, error) => {
          if (error) {
            console.error('hydration failed', error)
          } else {
            console.log('hydration finished')
          }
        }
      },
    },
  ),
)

export function Counter() {
  const count = useCounterStore((state) => state.count)
  const increment = useCounterStore((state) => state.increment)
  const [hydrated, setHydrated] = useState(
    useCounterStore.persist.hasHydrated(),
  )

  useEffect(() => {
    const unsubscribe = useCounterStore.persist.onFinishHydration(() => {
      setHydrated(true)
    })

    void useCounterStore.persist.rehydrate()

    return unsubscribe
  }, [])

  return (
    <section>
      <p>{hydrated ? `Saved count: ${count}` : 'Loading saved count…'}</p>
      <button type="button" onClick={increment}>
        Increment
      </button>
    </section>
  )
}
```

![The mounted counter shows the hydration state and the Increment button.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/77d909351a7de70b03ee7e57163f93a5.png)

On the first render, the component shows `Loading saved count…` and the store still contains `count: 0`. After `rehydrate()` finishes, the finish listener sets `hydrated` to `true`, and the component shows the stored count. The listener returns an unsubscribe function; returning it from the effect removes the listener when the component unmounts.

`hasHydrated()` is a non-reactive status check. Read it for the initial local value, then use `onFinishHydration` to update React state when the status changes. `rehydrate()` returns a promise, so you can also await it from application code when the render lifecycle is not the place to start hydration.

## Handle failures

Keep failure handling in the returned callback when you need the error and the completion handling in the same option. The callback receives the hydrated state and no error on success; on failure it receives an error instead. In the sample, success and failure are visible in the browser console, while the component's finish listener controls the rendered loading state.

If your storage is asynchronous, the initial render can still show the default state until hydration completes. Gate dependent UI with the same completion signal rather than assuming persisted values are present during the first render. For nested persisted objects, see [Merge persisted state](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/merge-persisted-state) before relying on the default shallow merge.

## Related

- [Persist store data](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/persist-store-data) — configure persistence and storage.
- [Handle server-rendered stores](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/handle-server-rendered-stores) — choose a store lifecycle for server-rendered applications.
- [Migrate persisted state](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/migrate-persisted-state) — handle stored data from an older version.
