SSR hydration (hydrate)
TL;DR: storage.hydrate(state) seeds the store with a ready snapshot. The main scenario is SSR: a server snapshot lands in the client store before the first render, with no flicker and no extra request.
Why
storage.hydrate(state) replaces the storage state with a ready snapshot. The server serializes state (for example, the first page of pokemon), the client initializes the storage with it — the first render already has data.
Sync storages (
MemoryStorage,LocalStorage):hydrate(state): voidAsync storages (
IndexedDBStorage):hydrate(state): Promise<void>
When to use
SSR/SSG: a server snapshot needs to be carried into the client store before the first render (no flicker, no re-fetch).
SPA with server data: swapping state on navigation (
hydrateafterinitialize()notifies subscribers).
When not to use
You work at the
createSynapse/createSynapseCtxmodule level — there the snapshot is seeded via thedehydratedStateprop, and a "bare"hydratecall isn't needed.The provider has no server data — there's nothing to hydrate; the C-form builds an empty store from
initialStateon the server anyway (see below).
Server → client flow
The same logic as a real Next.js page.tsx: on the server you fetch the first page and build a
serializable snapshot, on the client you seed the store with it before the first render.
// ── SERVER (Next.js Server Component / page.tsx) ──────────────────────────
// Fetch the first page of pokemon and build a store snapshot.
async function fetchFirstPokemonOnServer(): Promise<{ pokemonList: PokemonBrief[] }> {
const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=12&offset=0')
const data = await res.json()
const pokemonList = data.results.map((p) => {
const id = Number(p.url.split('/').filter(Boolean).pop())
return { id, name: p.name, sprite: `.../sprites/pokemon/${id}.png` }
})
return { pokemonList } // passed as a prop to the client component
}Hydration before initialize()
Called before initialize(), hydrate seeds the storage so that initialization does not
overwrite it with initialState — the server state wins.
import { MemoryStorage } from 'synapse-storage/core'
const storage = new MemoryStorage<{ pokemonList: PokemonBrief[] }>({
name: 'pokemon-ssr',
initialState: { pokemonList: [] }, // default for a "clean" client
})
// On the client: the snapshot arrived from the server as a prop
storage.hydrate(serverState)
await storage.initialize() // initialState will NOT overwrite the hydrated stateThe first client render already shows the pokemon list — no flicker and no second fetch.
Hydration after initialize()
Called after initialize(), hydrate replaces the state and notifies subscribers
(selectors and React hooks update reactively).
await storage.initialize()
// later, e.g. when navigating between pages in an SPA with server data
storage.hydrate(nextPageState)
// subscribers receive the new stateWith persist migrations
If a version is set, hydrate pins the current schema version: the
server snapshot is considered already up to date, so no migration runs on it.
React / createSynapse
hydrate is available on synapse.storage after the module is assembled:
const synapse = await pokemonSynapse.ready()
synapse.storage.hydrate(serverState)It is usually more convenient to work at the module level: createSynapseCtx builds
the snapshot via dehydrate and synchronously seeds the store through the dehydratedState prop (SSR is
enabled by construction — no ssr flag needed) — solving the same task for the whole module rather than a
bare storage.
For a provider that has no server data but still must not block SSR (a "background" shell over a
large subtree — presence, relations, a media-player), there is no snapshot to hydrate. In that case
nothing special is required: the C-form is
synchronous, so the module itself builds an empty store from initialState (buildSyncShell) on the
server so its children reach the HTML, then upgrades to the real store on the client (effects start in
the browser).
Types
interface ISyncStorage<T> {
hydrate(state: T): void
// ...
}
interface IAsyncStorage<T> {
hydrate(state: T): Promise<void>
// ...
}See also
Persist migrations
createSynapseCtx · Pokemon (full example)