Олег Марков
Интеграция Pinia для управления состоянием в Nuxt
Введение
При создании современных приложений на Vue часто возникает потребность в удобной, масштабируемой и простой в поддержке системе управления состоянием. На смену классическому Vuex сейчас приходит Pinia — легковесная и мощная библиотека, официально признанная основным state manager для Vue 3. В этом материале разберём процесс интеграции Pinia в проекты на Nuxt (особенно актуально для Nuxt 3), изучим основные принципы его работы, сравним с Vuex и посмотрим практические примеры настройки и использования хранилищ.
Будет полезно как для новичков, только осваивающих архитектуру state management, так и для опытных разработчиков, ищущих оптимальные подходы к построению архитектуры на новом стеке. Здесь вы найдёте готовые фрагменты кода, полезные советы и пояснения к основным понятиям.
Зачем нужен Pinia и почему он стал стандартом для Nuxt
Pinia — это современный инструмент для управления состоянием во Vue-приложениях. Если вы работали с Vuex, то заметите, что Pinia предлагает более простой API, поддержку Composition API "из коробки" и такие удобства, как поддержка типов для TypeScript без дополнительного кода.
Pinia был выбран официальным state-менеджером для Vue 3 по нескольким причинам:
- Простота интеграции.
- Меньше бойлерплейта (шаблонного кода).
- Расширенные опции для работы с асинхронностью.
- Безопасная и естественная работа с SSR (Server-Side Rendering), что особенно важно для Nuxt.
- Совместимость с tooling экосистемы Vue.
На фоне этих плюсов становится понятно, почему его рекомендуют использовать во всех новых Nuxt-проектах.
Как устроена интеграция Pinia в Nuxt
Для Nuxt 3 интеграция Pinia практически бесшовная — официальная документация сама рекомендует его как основной state manager. Если вы используете более старые версии Nuxt (2.x), подключение делается вручную через отдельный пакет и плагин. В этой статье мы будем разбирать именно подход Nuxt 3.
Интеграция Pinia предоставляет современный и удобный способ управления состоянием в Nuxt-приложениях, заменяя Vuex. Но просто подключить Pinia недостаточно, важно понимать, как правильно организовать структуру хранилища, как использовать actions и getters, и как интегрировать Pinia с другими частями вашего приложения. Если вы хотите освоить все тонкости интеграции Pinia в Nuxt, приходите на наш большой курс Nuxt - fullstack Vue фреймворк. На курсе 129 уроков и 13 упражнений, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.
Установка Pinia в проект Nuxt
Если вы только начинаете проект на Nuxt 3, можно добавить Pinia сразу при создании через опцию при запуске npx nuxi init
. Но во многих случаях потребуется интегрировать Pinia в уже существующий проект.
Вот как это делается:
# Устанавливаем @pinia/nuxt — специальный модуль для интеграции
npm install @pinia/nuxt
# или
yarn add @pinia/nuxt
После установки переходим к регистрации модуля в файле nuxt.config.ts
:
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@pinia/nuxt'
]
});
На этом базовое внедрение завершено — Pinia автоматически зарегистрируется как плагин во всё Nuxt-приложение.
Что происходит "под капотом"
После описанных выше шагов:
- При каждом запуске приложения создаётся собственный экземпляр Pinia-хранилища.
- Pinia становится доступен во всех компонентах, страницах, плагинах Nuxt.
- Все стейты (stores), созданные через Pinia, становятся универсальными — они корректно работают и на клиенте, и на сервере (SSR).
Теперь давайте разберёмся, как на практике создавать, использовать и организовывать хранилища.
Основные функции и возможности Pinia в Nuxt
Создание первого Pinia-стора
Pinia основывается на понятии "store" — это объект, который инкапсулирует некоторый state, геттеры и экшены.
В Nuxt принято размещать сторы в папке stores
. Допустим, вы хотите сделать глобальное хранилище для пользователя. Давайте создадим пример:
// stores/user.js
import { defineStore } from 'pinia'
// Здесь мы определяем store с именем 'user'
export const useUserStore = defineStore('user', {
// state — это функция, возвращающая объект состояния
state: () => ({
name: '',
loggedIn: false,
}),
// getters — вычисляемые свойства на основе state
getters: {
// Вычисляет длину имени пользователя
nameLength: (state) => state.name.length,
},
// actions — методы для изменения состояния и выполнения логики
actions: {
login(name) {
this.name = name // Изменяем состояние
this.loggedIn = true // Меняем статус входа
},
logout() {
this.name = ''
this.loggedIn = false
}
}
})
Пояснения по коду:
defineStore
— функция для создания стора.- Первый аргумент (
'user'
) — уникальное имя стора. state
— начальное состояние, как функция для предотвращения утечек состояния при SSR.getters
— вычисляемые свойства, которые кэшируются и автоматически обновляются.actions
— любые методы, которые изменяют state или содержат побочные эффекты.
Использование сторов в компонентах
Теперь, когда у нас есть store, давайте посмотрим, как использовать его в компонентах.
В компоненте Nuxt (или обычном Vue-файле) пишем так:
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// Пример использования
function loginUser() {
userStore.login('Alex') // Вызов action
}
</script>
<template>
<div>
<p>Имя пользователя: {{ userStore.name }}</p>
<p>Длина имени: {{ userStore.nameLength }}</p>
<button @click="loginUser">Войти</button>
<button @click="userStore.logout">Выйти</button>
</div>
</template>
Обратите внимание:
- В Pinia всё реактивно "из коробки" — повторные вызовы не нужны.
- Если использовать Composition API (через
<script setup>
), обращаться к store очень просто. - Все state, getters и actions доступны как обычные свойства.
Автоматическая типизация Pinia-сторов
Pinia полностью совместим с TypeScript. Если вы используете .ts
-файлы или TypeScript-поддержку в проекте Nuxt, сторы автоматически получают типы.
Пример:
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
}
})
Теперь при обращении к useCounterStore().count
вы получите числовой тип. Всё автокомплитится, что очень удобно для отладки.
Взаимодействие между разными сторами
Можно комбинировать сторы и обращаться к одному store из другого. Вот пример:
// stores/notifications.js
import { defineStore } from 'pinia'
export const useNotificationStore = defineStore('notifications', {
state: () => ({ messages: [] }),
actions: {
add(message) {
this.messages.push(message)
}
}
})
// stores/tasks.js
import { defineStore } from 'pinia'
import { useNotificationStore } from './notifications'
export const useTasksStore = defineStore('tasks', {
state: () => ({ tasks: [] }),
actions: {
addTask(task) {
this.tasks.push(task)
// Получаем доступ к другому store
const notifications = useNotificationStore()
notifications.add(`Задача добавлена: ${task.title}`)
}
}
})
Это очень удобно для организации логики уведомлений, авторизации, кастомной работы с ошибками и других кросс-модулей.
Использование Pinia с SSR и Nuxt middleware
Pinia изначально проектировалась так, чтобы правильно работать с серверным рендерингом, не допуская утечек состояния между запросами пользователей.
Глобальный доступ к сторам
В коде Nuxt вы можете использовать свой стор прямо в middleware, хуках и других местах, вот пример использования store в middleware:
// middleware/auth.js
import { useUserStore } from '@/stores/user'
export default defineNuxtRouteMiddleware((to, from) => {
const userStore = useUserStore()
if (!userStore.loggedIn && to.name !== 'login') {
// Перенаправляем на страницу логина
return navigateTo('/login')
}
})
Такой подход позволяет централизовать логику авторизации.
Асинхронные actions и запросы к API
В Pinia actions могут быть асинхронными просто через async
. Давайте рассмотрим пример:
// stores/posts.js
import { defineStore } from 'pinia'
export const usePostsStore = defineStore('posts', {
state: () => ({
posts: [],
loading: false,
error: null,
}),
actions: {
async fetchPosts() {
this.loading = true
this.error = null
try {
// Здесь используем $fetch — фичу Nuxt для запросов к API
this.posts = await $fetch('/api/posts')
} catch (e) {
this.error = e.message
} finally {
this.loading = false
}
}
}
})
В компонентах можно вызывать такие actions точно так же, как синхронные:
<script setup>
import { usePostsStore } from '@/stores/posts'
const postsStore = usePostsStore()
onMounted(() => {
postsStore.fetchPosts() // Автоматически загружаем список постов при монтировании компонента
})
</script>
Реактивность и использование storeToRefs
Pinia предоставляет функцию storeToRefs
, чтобы корректно извлекать реактивные свойства state и getters без потери реактивности. Это может быть полезно для деструктуризации:
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// Получаем только нужные реактивные свойства
const { name, loggedIn } = storeToRefs(userStore)
Модули и организация стора
В отличие от Vuex, Pinia не навязывает иерархических модулей. Каждый store — независимая сущность. Лучше создавать сторы по сущностям или доменам приложения (например, user, posts, notifications).
Для крупных проектов удобно организовывать сторы по подпапкам внутри stores
. Нет необходимости вручную регистрировать каждый store — Pinia всё найдёт автоматически.
SSR, hydratation и синхронизация состояния между сервером и клиентом
Pinia совместно с Nuxt заботится о детальной синхронизации:
- State генерируется на сервере и отправляется вместе с HTML.
- На клиенте состояние автоматически гидратируется (синхронизируется).
- Происходит правильная изоляция состояний между пользователями на сервере.
Для большинства задач не нужно заботиться о гидратации вручную, однако в некоторых случаях (если нужны сложные мутации сторы при запуске) стоит учитывать сервер-клиентное разделение.
Заключение
Интеграция Pinia с Nuxt — это быстрый и надёжный способ реализации стейт-менеджмента в современных Vue-приложениях. Благодаря простому API, поддержке SSR и тесной интеграции с экосистемой Nuxt, Pinia становится оптимальным выбором для большинства задач — от простых до сложных систем. Здесь мы рассмотрели процесс установки, базовые и продвинутые особенности, а также подходы к организации кода. Внедряя Pinia, вы получаете мощный инструмент с хорошей документацией и поддержкой, который ускоряет разработку и делает поддержку состояния приложения более удобной.
Интеграция Pinia - это отличный способ улучшить управление состоянием в ваших Nuxt-приложениях. Чтобы создавать действительно мощные и масштабируемые решения, необходимо освоить все аспекты фреймворка, включая работу с сервером, базами данных и API. На нашем курсе Nuxt - fullstack Vue фреймворк вы найдете все необходимые знания и навыки для достижения успеха. В первых 3 модулях уже доступно бесплатное содержание — начните погружаться в Nuxt прямо сегодня.
Частозадаваемые технические вопросы по теме
1. Как использовать Pinia-хранилище в плагинах Nuxt?
В плагине можно импортировать определённый store напрямую и использовать как обычно. Пример для plugins/myPlugin.ts
:ts
import { useUserStore } from '@/stores/user'
export default defineNuxtPlugin(() => {
const userStore = useUserStore()
// Используйте userStore здесь для операций
})
2. Как сбросить состояние store Pinia на значения по умолчанию?
Внутри каждого стора добавьте action для сброса:
js
reset() {
this.$reset() // встроенный метод сброса до изначального состояния
}
3. Как подписываться на изменения состояния (watch state)?
Можно использовать функцию watch
из Vue:
js
import { watch } from 'vue'
watch(() => userStore.loggedIn, (newVal) => {
// Реагируем на изменение loggedIn
})
4. Как подключить сторонние плагины или расширения к Pinia?
Создайте отдельный плагин в папке plugins, используйте функцию pinia.use:
js
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.$pinia.use((context) => {
// Ваш код плагина Pinia
})
})
5. Как использовать динамические store (например, store для каждой вкладки окна браузера)?
Pinia поддерживает создание раздельных store-инстансов через функцию defineStore(id, options)
. Используйте уникальный id для каждой вкладки или типа данных, чтобы стейт не пересекался.
Постройте личный план изучения Nuxt до уровня Middle — бесплатно!
Nuxt — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Nuxt
Лучшие курсы по теме

Nuxt
Антон Ларичев
TypeScript с нуля
Антон Ларичев