Skip to the solution
MediumFrontend10 min read

Pinia colocation in 2025 — how do you colocate stores with routes/components?

1.1k views

The problem

Question presented to candidate: "Our src/stores/ folder has forty Pinia stores and nobody knows which page owns which. How would you colocate stores with the routes and components that use them, and what does that change for bundle size and store lifetime?"

What a strong answer should cover:

  • Colocation: keep a feature store next to the feature that owns it (features/users/users.store.ts beside UsersPage.vue); keep only truly app-wide stores (session, settings) in a shared folder.
  • With lazy routes, a store imported only by one route's components ships in that route's chunk; a store imported by two lazy routes is split into a shared chunk. Stores are created on first useStore(), not at import.
  • Store ids are global per Pinia: two features using the same id silently get the same store, so namespace ids (users-feature) or derive them from the folder.
  • Lifetime: stores live until disposed; for feature-scoped data either reset or $dispose() on leaving the feature (Pinia 4.1 warns when you dispose a stale reference), or use component-scoped state.
  • Not all feature state needs a store: state for one component subtree can be provide/inject or a composable, which gives one instance per subtree.

Clarifying questions expected:

  • "Is this a plain Vite SPA or Nuxt (which has its own store auto-import conventions)?"
  • "Should a feature keep its data when the user navigates away and comes back?"

Code / implementation expected: Optional — a folder layout, a colocated setup store, and the lazy route that uses it.

  • pinia
  • state
  • colocation

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read · 10 sections

Read the code

VueFeature-scoped state with provide/inject: two flows, two independent states
<script setup>
// Colocating state with a component subtree instead of a global store:
// each <CheckoutFlow> owns its own state, and only its descendants see it.
// A Pinia store with one id would be shared by both flows instead.
import { defineComponent, h, provide, inject, reactive, computed } from "vue";

const FLOW = Symbol("checkout-flow"); // private key: lives next to the feature

function createFlowState(name) {
  const state = reactive({ name, step: 1, items: 0 });
  const canPay = computed(() => state.items > 0);
  return { state, canPay, addItem: () => state.items++, next: () => state.step++ };
}

const CartStep = defineComponent({
  setup() {
    const flow = inject(FLOW);
    return () => h("button", { onClick: flow.addItem }, `${flow.state.name}: add item (${flow.state.items})`);
  },
});

const PayStep = defineComponent({
  setup() {
    const flow = inject(FLOW);
    return () =>
      h("button", { disabled: !flow.canPay.value, onClick: flow.next }, `${flow.state.name}: pay (step ${flow.state.step})`);
  },
});

const CheckoutFlow = defineComponent({
  props: { name: String },
  setup(props) {
    provide(FLOW, createFlowState(props.name)); // one state per flow instance
    return () => h("section", [h(CartStep), h(PayStep)]);
  },
});
</script>

<template>
  <CheckoutFlow name="Flow A" />
  <CheckoutFlow name="Flow B" />
</template>
A colocated feature store: features/users/users.store.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
export const useUsersStore = defineStore("users-feature", () => {
  const list = ref([]);
  const count = computed(() => list.value.length);
  async function load() { list.value = [{ id: 1, name: "Ada" }, { id: 2, name: "Linus" }]; }
  return { list, count, load };
});

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 24 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextMedium

What do defineOptions() and defineSlots() do in <script setup>?

Continue