useStore() усередині setup знаходить активний екземпляр Pinia через inject. Поза компонентом (у guard роутера, інтерсепторі HTTP-клієнта, звичайному модулі) інжекту немає, і Pinia покладається на «активний» екземпляр, встановлений app.use(pinia).
Типова помилка - виклик на рівні модуля:
// api.js
const auth = useAuthStore(); // виконується при імпорті - ДО app.use(pinia)
// Error: getActivePinia() was called but there was no active Pinia
Правильно - викликати всередині функції, яка виконається пізніше:
router.beforeEach((to) => {
const auth = useAuthStore(); // на момент навігації Pinia вже встановлена
if (to.meta.requiresAuth && !auth.isLoggedIn) return '/login';
});
http.interceptors.request.use((config) => {
const auth = useAuthStore();
if (auth.token) config.headers.Authorization = `Bearer ${auth.token}`;
return config;
});
Чому це критично в SSR. На сервері Node.js обслуговує багато запитів одним процесом. Якщо стор створюється на рівні модуля чи покладається на глобальний «активний» екземпляр, стан одного користувача може потрапити в запит іншого: кошик, профіль, токен.
Правила для SSR:
- новий екземпляр Pinia (і роутера, і застосунку) на кожен запит - у фабричній функції
createApp(); - поза
setupпередаватиpiniaявно:
router.beforeEach((to) => {
const auth = useAuthStore(pinia); // саме той екземпляр, що належить цьому запиту
});
- жодного стану на рівні модуля (
let currentUserв імпортованому файлі) - він спільний для всіх запитів процесу.
Ще одна пастка - стори, що використовують один одного. Виклик useOtherStore() у тілі setup store - нормально, але циклічне читання стану двох сторів одне з одного під час створення призводить до помилок. Доступ до іншого стору краще робити в гетерах і діях, а не на верхньому рівні.
Тестування: у юніт-тестах сторів перед кожним тестом - setActivePinia(createPinia()), щоб тести не ділили стан.
Докладніше в документації: Pinia: використання поза компонентами