Vaxee: A Simpler, Fetch-Aware State Manager for Vue and Nuxt

When you think of state management in Vue.js or Nuxt.js, Pinia is the go-to library. It’s solid — but when working in Nuxt, managing data fetching and SSR often feels clunky. To keep server/client state in sync, I usually end up mixing callOnce(), useFetch(), or useAsyncData() in awkward ways.

Recently, I discovered Vaxee — a lightweight state manager that treats fetching as a first-class concept. It simplifies store logic, server-side fetching, and hydration — all while staying small and intuitive.


What Makes Vaxee Special

Vaxee gives you a compact createStore() factory and three simple primitives:

The magic lies in request(): it keeps fetching logic inside the store, exposing helpers like data, refresh, and status for easy use in pages.

Defining a Store

Vaxee’s API centers around createStore(name, setup), where all your state, getters, and requests live together.

stores/user.ts
import { createStore } from 'vaxee';

export const useUserStore = createStore('user', ({ state, getter, request }) => {
  const tokens = state({ access: '', refresh: '' }, { persist: 'user.tokens' });

  const user = request(({signal}) => $fetch('/api/user', { signal }));

  const fullName = getter(() => user.data.value ? `${user.data.value?.first_name} ${user.data.value?.last_name}` : undefined);

  return { tokens, fullName, user };
});

This single file holds everything: state, derived data, and fetch logic.

The request() Advantage

Unlike Pinia, where i often need to take some data fetching logics outside the store, request() keeps it internal. Each request exposes:

  • data, error, execute, status, refresh() etc for easy use in components and pages.

It even supports parameters:

const user = request<User, { id: number }>(({ signal, params }) =>
  $fetch(`/api/user/${params.id}`, { signal }));

Full Example — Store + Page

import { createStore } from 'vaxee';

export const useUserStore = createStore('user', ({ state, getter, request }) => {
  const tokens = state({ access: '', refresh: '' }, { persist: 'user.tokens' });

  const user = request(({signal}) => $fetch('/api/user', { signal }));

  const fullName = getter(() => user.data.value ? `${user.data.value?.first_name} ${user.data.value?.last_name}` : undefined);

  return { tokens, fullName, user };
});

This setup keeps the fetch logic in the store while the page simply consumes the data.


TL;DR

  • Pinia: Reliable, flexible, but fetch coordination in Nuxt can be verbose.
  • Vaxee: Minimal and fetch-aware by design.
  • If you want cleaner, colocated state + fetch logic — give Vaxee a try.