Reactive Headless Data Models in Nuxt
Hey all! Amateur programmer here. I've got an issue that I run into all the time, and I haven't found a solution yet. The issue is this: do people use composables to represent reactive class structures for their complex data models on the client? If not, what should I be doing?
Background: I don't know where to keep my client-side data models. I know useState and Pinia are recommended but they seem to be more for app state, like app-wide UI elements, and I think they only work well for primitive data types. My data types are often heavily graph-based (as in graph theory), and so they tend to have complex dependency structures (across edge relations, which - similar question - how do I store those in my app?)
I could just have my UI components handle all the dynamic data computation, but that leads to a lot of repeated code and processing. It would be better to have some kind of stateful headless instancing of these dynamic data models - which is maybe where composables come into play? So, is it bad to use composables as reactive JS Class structures, to decouple data from the UI, but also persist it on the client in a more computed way than primitive data in Pinia stores?
2 Replies
Hi @Carson. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
----------------------
This is a great and nuanced question about managing complex, reactive data models in Nuxt. Here’s what the knowledge sources say:
1. Composables and Reactivity
Nuxt’s composables are a way to encapsulate logic and state, and they are auto-imported from the
It’s not “bad” to use composables for reactive, headless data models in Nuxt, but you must be aware of the serialization limitations of SSR state. For class-based or complex data, use custom payload plugins to handle serialization. For most use cases, composables and Pinia (with plain objects/arrays) are sufficient and recommended. If you need to persist class instances, follow the advanced serialization approach described above. If you need more concrete examples or run into specific issues with serialization, let me know! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
composables/
directory. However, composables themselves do not provide additional reactivity; they rely on Vue’s Composition API (e.g., ref
, reactive
) for reactivity. You can use composables to organize and reuse logic, including managing complex data structures, but the reactivity is handled by Vue, not by Nuxt’s composable system itself. You are free to use reactivity features wherever needed, not just in composables. If you want to auto-import composables from nested directories, you need to configure this in your nuxt.config.ts
or re-export them from an index file composables directory docs.
2. useState and Serialization
useState
is Nuxt’s SSR-friendly way to create shared, reactive state. However, it serializes data to JSON between server and client, which means it does not support storing classes, functions, or symbols—only plain objects and primitives. If you try to store class instances, you’ll run into serialization issues. For complex data (like graphs), you’d need to ensure your data can be serialized, or use custom payload revivers/serializers (see below) useState docs.
3. Storing Classes and Complex Data
If you need to use class instances (for example, for graph structures), Nuxt 3 allows you to define custom payload reducers and revivers to serialize and revive class instances during SSR hydration. This is an advanced feature, but it’s the recommended way to handle non-POJO (plain old JavaScript object) data in Nuxt’s SSR context. You can define a reducer (serializer) and a reviver (deserializer) for your class, so it can be safely passed between server and client useNuxtApp payload docs.
Example (from the docs):
This approach is also discussed in the Class Serialization with SSR in Nuxt video.
4. Pinia and State Management
Pinia is the recommended state management library for Vue 3 and Nuxt 3. It’s not limited to primitives—you can store objects, arrays, and even more complex structures, as long as they are serializable. However, the same caveat applies: if you need to persist class instances across SSR, you’ll need to handle serialization yourself state management docs.
5. Where to Put Your Data Models
- Composables: Good for encapsulating logic and creating reusable, reactive hooks. You can use them to manage and expose complex data models, as long as you handle reactivity and serialization properly.
- Pinia/useState: Good for app-wide state, including complex objects, but be mindful of serialization limits.
- Custom Serialization: For class-based models, use custom payload plugins to serialize/deserialize your data.
6. Graph Structures and Relations
You can represent graph structures using plain objects/arrays, or with classes if you implement custom serialization. The key is to ensure that whatever you store in SSR-friendly state (like useState
or Pinia) can be serialized and revived on the client.
---
Summary:It’s not “bad” to use composables for reactive, headless data models in Nuxt, but you must be aware of the serialization limitations of SSR state. For class-based or complex data, use custom payload plugins to handle serialization. For most use cases, composables and Pinia (with plain objects/arrays) are sufficient and recommended. If you need to persist class instances, follow the advanced serialization approach described above. If you need more concrete examples or run into specific issues with serialization, let me know! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
<@547540321811300352> Kapa.ai is still learning and improving, please let me know how I did by reacting below.