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.
Important Limitations
- Data models should have a unique field named
id, it can be a number or string. - Data structures returned by the server are serialized via
superjson, so dates for example are not converted to strings. - The zenstack generated files should be located at project root under
zenstackfolder. - When zenstack schema is re-generated the app should be restarted.
- The
useZenstackUpdateanduseZenstackCreatecomposables do not allow relational fields. - Update and delete operations triggered by cascading operations are not handled by realtime subscriptions, and the store does not get updated automatically.
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.
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
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.