Store & Normalization
Nuxt Zenstack uses a centralized, normalized store to manage your application's data. This ensures that your state remains consistent, prevents duplicate data from being stored, and makes UI updates reactive across your entire application.
The useZenstackStore Composable
At the core of data management is the useZenstackStore composable. Whenever you fetch, create, update, or delete data using the provided CRUD composables, the changes are automatically reflected in this centralized store.
const { state, getOne, getMany } = useZenstackStore();The store provides methods to interact with the raw normalized data, though you will typically interact with it through the higher-level CRUD composables (like useZenstackRead or useZenstackCreate).
How Normalization Works
When data is fetched from your API (which often contains nested relationships), Nuxt Zenstack processes it before storing it.
- Flattening with
normalizr: The nested JSON response is flattened into a dictionary of entities, grouped by their model name and ID. If multiple queries return the same entity (e.g., a specificUser), only one copy of that entity is kept in the store. - Merging Data: When new data is fetched or a mutation occurs, the store intelligently merges the incoming data with the existing state, preserving existing fields while updating changed ones.
NOTE
Data structures returned by the server are serialized via superjson, so dates for example are preserved as Date objects and not converted to strings.
Structure of the Store
The underlying state structure looks like this:
{
"User": {
"user_1": { id: "user_1", name: "Alice", posts: ["post_1", "post_2"] }
},
"Post": {
"post_1": { id: "post_1", title: "Hello World", authorId: "user_1" },
"post_2": { id: "post_2", title: "Nuxt is awesome", authorId: "user_1" }
}
}Advanced Features
Bidirectional Relationship Linking
Normalizing data usually requires the payload to explicitly contain both sides of a relationship. Nuxt Zenstack goes a step further by automatically linking relationships based on your ZenStack schema.
For example, if you create a new Post and only provide the authorId, the store will automatically:
- Set the
authorfield on thePost. - Push the new
PostID into thepostsarray of the correspondingUser.
This means that any UI components displaying the user's posts will immediately re-render with the new post, without needing a full refetch.
Circular Reference Handling
Because relationships are inherently circular (a User has Posts, and a Post has a User), returning raw denormalized data can cause issues with Vue's reactivity system and JSON serialization (such as during SSR context transfer).
When you read data from the store, Nuxt Zenstack automatically breaks these circular references safely, ensuring your app won't crash when rendering complex relationship trees.
Fetch History Tracking & SSR Caching
The store also keeps track of all data fetching operations (fetchHistory), differentiating between requests made on the server (SSR) and on the client. This is useful for debugging and optimizing your data-loading strategies.
Most importantly, server data is cached by its HTTP URL (which includes the model, method, and query parameters). During client hydration, the composables inspect this history. If an exact match for the HTTP URL is found from a server fetch, the client will skip re-fetching it and immediately use the cached data, preventing redundant network requests.