Vue 3 Pinia vs Vuex ์๋ฒฝ ๋น๊ต: 2026๋ ์ํ ๊ด๋ฆฌ ์ ๋ต๊ณผ ๋ฉด์ ํต์ฌ ์ง๋ฌธ
Vue 3 ์ํ๊ณ์์ Pinia์ Vuex๋ฅผ ๋น๊ต ๋ถ์ํฉ๋๋ค. Options Store์ Setup Store ํจํด, TypeScript ํตํฉ, ํฌ๋ก์ค ์คํ ์ด ๊ตฌ์ฑ, SSR ์ง์, Vuex์์ Pinia๋ก์ ๋ง์ด๊ทธ๋ ์ด์ ์ ๋ต, ๊ทธ๋ฆฌ๊ณ 2026๋ ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ ์ํ ๊ด๋ฆฌ ์ง๋ฌธ์ ์ฝ๋ ์์ ์ ํจ๊ป ์ ๋ฆฌํฉ๋๋ค.

Vue 3์ ๊ณต์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Pinia๋ Vuex 4๋ฅผ ๋์ฒดํ๋ ์ฐจ์ธ๋ ์๋ฃจ์ ์ผ๋ก ์๋ฆฌ ์ก์์ต๋๋ค. Vue ์ฝ์ด ํ์ด ๊ณต์์ ์ผ๋ก ๊ถ์ฅํ๋ Pinia๋ Composition API์์ ์์ฐ์ค๋ฌ์ด ํตํฉ, ์์ ํ TypeScript ์ถ๋ก , ๊ทธ๋ฆฌ๊ณ ๋ฎคํ ์ด์ ์๋ ์ง๊ด์ API๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด ๊ธ์์๋ Pinia์ Vuex์ ์ํคํ ์ฒ์ ์ฐจ์ด๋ฅผ ์ค์ ์ฝ๋๋ก ๋น๊ตํ๊ณ , ํ๋ก๋์ ํ๊ฒฝ์์์ ๋ง์ด๊ทธ๋ ์ด์ ์ ๋ต๊ณผ 2026๋ ํ๋ฐํธ์๋ ๋ฉด์ ์์ ๋น์ถ๋๋ ์ํ ๊ด๋ฆฌ ์ง๋ฌธ์ ์ฒด๊ณ์ ์ผ๋ก ์ ๋ฆฌํฉ๋๋ค.
Pinia 3๋ Vue 3์ ๊ณต์ ์ํ ๊ด๋ฆฌ ์๋ฃจ์ ์ ๋๋ค. ๋ฎคํ ์ด์ ์ด ์ ๊ฑฐ๋์ด ์ง์ ์ํ ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ๋ฉฐ, TypeScript ํ์ ์ถ๋ก ์ด ์๋์ผ๋ก ๋์ํฉ๋๋ค. ๋ฒ๋ค ํฌ๊ธฐ๋ gzip ๊ธฐ์ค ์ฝ 1KB๋ก Vuex 4(์ฝ 6KB)์ 6๋ถ์ 1 ์์ค์ ๋๋ค. ์ ๊ท ํ๋ก์ ํธ์์๋ Pinia๋ฅผ ์ ํํ๊ณ , ๊ธฐ์กด Vuex ํ๋ก์ ํธ๋ ์ ์ง์ ๋ง์ด๊ทธ๋ ์ด์ ์ ๊ถ์ฅํฉ๋๋ค.
Pinia์ ๋ ๊ฐ์ง ์คํ ์ด ํจํด: Options Store vs Setup Store
Pinia๋ ์คํ ์ด๋ฅผ ์ ์ํ๋ ๋ ๊ฐ์ง ๋ฐฉ์์ ์ ๊ณตํฉ๋๋ค. Options Store๋ Vuex์ ์ต์ํ ๊ฐ๋ฐ์๊ฐ ๋น ๋ฅด๊ฒ ์ ์ํ ์ ์๋ ๊ตฌ์กฐ์ด๋ฉฐ, Setup Store๋ Composition API ํจํด์ ๊ทธ๋๋ก ํ์ฉํฉ๋๋ค.
Options Store
Options Store๋ state, getters, actions ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ์คํ ์ด๋ฅผ ์ ์ํฉ๋๋ค. Vuex์ ๊ตฌ์กฐ์ ์ ์ฌํ์ง๋ง ๋ฎคํ
์ด์
์ด ์ ๊ฑฐ๋์ด, ์ก์
๋ด์์ this๋ฅผ ํตํด ์ํ๋ฅผ ์ง์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
import { defineStore } from 'pinia'
// Options Store syntax โ familiar to Vuex developers
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
lastUpdated: null as Date | null,
}),
getters: {
// Getters receive state as first argument with full type inference
doubleCount: (state) => state.count * 2,
isPositive(): boolean {
// Access other getters via `this`
return this.count > 0
},
},
actions: {
increment() {
// Direct state mutation โ no commit() needed
this.count++
this.lastUpdated = new Date()
},
async fetchCount(id: string) {
// Async actions work without extra configuration
const response = await fetch(`/api/counters/${id}`)
const data = await response.json()
this.count = data.count
},
},
})Options Store์์ getter๋ ์ฒซ ๋ฒ์งธ ์ธ์๋ก state๋ฅผ ๋ฐ์ผ๋ฉฐ ์์ ํ ํ์
์ถ๋ก ์ด ์ ์ฉ๋ฉ๋๋ค. ๋ค๋ฅธ getter์ ์ ๊ทผํ ๋๋ this๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ก์
์์๋ commit()์ด๋ dispatch() ์์ด this๋ฅผ ํตํด ์ํ๋ฅผ ์ง์ ๋ณ๊ฒฝํฉ๋๋ค. ๋น๋๊ธฐ ์ก์
๋ ์ถ๊ฐ ์ค์ ์์ด async/await ํจํด์ ๊ทธ๋๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
Setup Store
Setup Store๋ Composition API์ ref, computed, ์ผ๋ฐ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์คํ ์ด๋ฅผ ์ ์ํฉ๋๋ค. ์ปดํฌ๋ํธ์ <script setup>๊ณผ ๋์ผํ ํจํด์ด๋ฏ๋ก, Composition API์ ์ต์ํ ํ์์๋ ํ์ต ๋น์ฉ์ด ๊ฑฐ์ ์์ต๋๋ค.
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// Setup Store syntax โ identical patterns to Composition API
export const useCounterStore = defineStore('counter', () => {
// ref() becomes state
const count = ref(0)
const lastUpdated = ref<Date | null>(null)
// computed() becomes getters
const doubleCount = computed(() => count.value * 2)
const isPositive = computed(() => count.value > 0)
// Functions become actions
function increment() {
count.value++
lastUpdated.value = new Date()
}
async function fetchCount(id: string) {
const response = await fetch(`/api/counters/${id}`)
const data = await response.json()
count.value = data.count
}
// Must return all state, getters, and actions
return { count, lastUpdated, doubleCount, isPositive, increment, fetchCount }
})Setup Store์ ํต์ฌ ์ด์ ์ ์ ์ฐ์ฑ์
๋๋ค. ref()๊ฐ ์ํ, computed()๊ฐ getter, ์ผ๋ฐ ํจ์๊ฐ ์ก์
์ผ๋ก ๋งคํ๋๋ฏ๋ก, ์ธ๋ถ ์ปดํฌ์ ๋ธ์ ์คํ ์ด ๋ด๋ถ์์ ์ง์ ์ฌ์ฉํ ์ ์์ต๋๋ค. Options Store์์๋ ๋ถ๊ฐ๋ฅํ watch, watchEffect, VueUse ์ปดํฌ์ ๋ธ ๋ฑ์ ํ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
TypeScript ํตํฉ: Pinia vs Vuex
TypeScript ์ง์์ Pinia์ Vuex ์ฌ์ด์ ๊ฐ์ฅ ๋๋๋ฌ์ง ์ฐจ์ด ์ค ํ๋์ ๋๋ค. Vuex 4์์๋ ์คํ ์ด์ ํ์ ์ ์๋์ผ๋ก ์ ์ธํด์ผ ํ๋ฉฐ, ๋ชจ๋ ์ฆ๊ฐ(module augmentation)์ด ํ์์ ์ ๋๋ค.
// Vuex 4 โ manual type augmentation required
import { Store } from 'vuex'
declare module 'vuex' {
interface State {
counter: {
count: number
lastUpdated: Date | null
}
}
}
// Accessing state requires type assertions or custom helpers
const count = (store.state as { counter: { count: number } }).counter.countVuex์์๋ commit('SET_COUNT', value) ํธ์ถ ์ ๋ฎคํ
์ด์
์ด๋ฆ์ด ๋ฌธ์์ด์ด๋ฏ๋ก, ์คํ๊ฐ ์์ด๋ ์ปดํ์ผ ํ์์ ์กํ์ง ์์ต๋๋ค. ํ์
์์ ์ฑ์ ํ๋ณดํ๋ ค๋ฉด ๋ณ๋์ ํฌํผ ํจ์๋ ํ์
๊ฐ๋๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
Pinia์์๋ ์ด๋ฌํ ๋ณด์ผ๋ฌํ๋ ์ดํธ๊ฐ ์์ ํ ์ ๊ฑฐ๋ฉ๋๋ค.
// Pinia โ full type inference, zero configuration
const counter = useCounterStore()
// TypeScript knows counter.count is number
// TypeScript knows counter.doubleCount is number
// TypeScript knows counter.increment() returns void
// TypeScript knows counter.fetchCount() returns Promise<void>
counter.increment()
console.log(counter.doubleCount)defineStore์์ ๋ฐํ๋ ์ปดํฌ์ ๋ธ์ ์ํ, getter, ์ก์
์ ํ์
์ ์๋์ผ๋ก ์ถ๋ก ํฉ๋๋ค. ๋ชจ๋ ์ฆ๊ฐ์ด ๋ถํ์ํ๋ฉฐ, IDE์ ์๋ ์์ฑ๊ณผ ๋ฆฌํฉํ ๋ง ๋๊ตฌ๊ฐ ์๋ฒฝํ๊ฒ ๋์ํฉ๋๋ค. ๋๊ท๋ชจ ์ฝ๋๋ฒ ์ด์ค์์ ์ด ์ฐจ์ด๋ ๊ฐ๋ฐ ์์ฐ์ฑ์ ์๋นํ ์ํฅ์ ๋ฏธ์นฉ๋๋ค.
์ปดํฌ์ ๋ธ๊ณผ ๊ฒฐํฉํ ๊ณ ๊ธ ์คํ ์ด ํจํด
Setup Store์ ๊ฐ์ฅ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ Vue ์ปดํฌ์ ๋ธ๊ณผ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์คํ ์ด ๋ด๋ถ์์ ์ง์ ์ฌ์ฉํ ์ ์๋ค๋ ์ ์ ๋๋ค. ์๋ ์์ ๋ VueUse์ ๋๋ฐ์ด์ค ํจ์์ Vue Router๋ฅผ ๊ฒฐํฉํ ๊ฒ์ ์คํ ์ด์ ๋๋ค.
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'
import { useRoute } from 'vue-router'
export const useSearchStore = defineStore('search', () => {
const route = useRoute()
const query = ref('')
const results = ref<SearchResult[]>([])
const isLoading = ref(false)
// VueUse composable used directly inside the store
const debouncedSearch = useDebounceFn(async (term: string) => {
if (!term.trim()) {
results.value = []
return
}
isLoading.value = true
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(term)}`)
results.value = await res.json()
} finally {
isLoading.value = false
}
}, 300)
// Watcher syncs URL query param to store state
watch(() => route.query.q, (q) => {
if (typeof q === 'string') {
query.value = q
debouncedSearch(q)
}
})
function setQuery(term: string) {
query.value = term
debouncedSearch(term)
}
return { query, results, isLoading, setQuery }
})์ด ํจํด์์ useDebounceFn์ VueUse์์ ์ ๊ณตํ๋ ๋๋ฐ์ด์ค ์ปดํฌ์ ๋ธ๋ก, ๊ฒ์ API ํธ์ถ์ 300ms ๊ฐ๊ฒฉ์ผ๋ก ์ ํํฉ๋๋ค. watch๋ URL์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ๋ณ๊ฒฝ์ ๊ฐ์งํ์ฌ ์คํ ์ด ์ํ์ ๋๊ธฐํํฉ๋๋ค. Options Store์์๋ ์ด๋ฌํ ์กฐํฉ์ด ๋ถ๊ฐ๋ฅํ๋ฏ๋ก, ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ด ํ์ํ ๊ฒฝ์ฐ Setup Store๊ฐ ์ ํฉํฉ๋๋ค.
ํฌ๋ก์ค ์คํ ์ด ํต์
Pinia์์ ์คํ ์ด ๊ฐ ํต์ ์ ์ปดํฌ์ ๋ธ ํธ์ถ๋ก ์ด๋ฃจ์ด์ง๋๋ค. Vuex์ ๋ค์์คํ์ด์ค ๋ชจ๋ ๊ฐ dispatch('auth/logout', null, { root: true }) ๊ฐ์ ๋ณต์กํ ํจํด ๋์ , ๋ค๋ฅธ ์คํ ์ด์ ์ปดํฌ์ ๋ธ์ ์ง์ ํธ์ถํฉ๋๋ค.
import { defineStore } from 'pinia'
import { useProductStore } from './products'
import { useAuthStore } from './auth'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const total = computed(() => {
// Access another store by calling its composable
const productStore = useProductStore()
return items.value.reduce((sum, item) => {
const product = productStore.getById(item.productId)
return sum + (product?.price ?? 0) * item.quantity
}, 0)
})
async function checkout() {
const auth = useAuthStore()
if (!auth.isAuthenticated) {
throw new Error('Authentication required')
}
// Checkout logic
}
return { items, total, checkout }
})computed ๋ด๋ถ์์ useProductStore()๋ฅผ ํธ์ถํ๋ฉด, ์ํ ์คํ ์ด์ ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋ total์ด ์๋์ผ๋ก ์ฌ๊ณ์ฐ๋ฉ๋๋ค. Vuex์์ rootGetters๋ฅผ ํตํด ๋ค๋ฅธ ๋ชจ๋์ ์ ๊ทผํ๋ ๋ฐฉ์์ ๋นํด ์ฝ๋์ ๋ช
์์ฑ๊ณผ ํ์
์์ ์ฑ์ด ํฌ๊ฒ ํฅ์๋ฉ๋๋ค.
Vue.js / Nuxt.js ๋ฉด์ ์ค๋น๊ฐ ๋์ จ๋์?
์ธํฐ๋ํฐ๋ธ ์๋ฎฌ๋ ์ดํฐ, flashcards, ๊ธฐ์ ํ ์คํธ๋ก ์ฐ์ตํ์ธ์.
Pinia vs Vuex ๊ธฐ๋ฅ ๋น๊ตํ
| ๊ธฐ๋ฅ | Pinia 3 | Vuex 4 | |------|---------|--------| | ๋ฎคํ ์ด์ | ์์ (์ง์ ์ํ ๋ณ๊ฒฝ) | ์ํ ๋ณ๊ฒฝ ์ ํ์ | | TypeScript | ์์ ํ ํ์ ์ถ๋ก , ์ฆ๊ฐ ๋ถํ์ | ์๋ ํ์ ์ฆ๊ฐ ํ์ | | ์คํ ์ด ์ํคํ ์ฒ | ๋ค์์ ํ๋ซ ์คํ ์ด | ์ค์ฒฉ ๋ชจ๋์ ๊ฐ์ง ๋จ์ผ ์คํ ์ด | | Composition API | ๋ค์ดํฐ๋ธ ์ง์ | Options API ๊ธฐ๋ฐ | | ๋ฒ๋ค ํฌ๊ธฐ | gzip ์ฝ 1KB | gzip ์ฝ 6KB | | Vue Devtools | ์์ ์ง์ (v7) | ์์ ์ง์ | | SSR | ๋ด์ฅ ์ง์ | ๋ณ๋ ์ค์ ํ์ | | Hot Module Replacement | ๋ด์ฅ ์ง์ | ์๋ ์ค์ ํ์ |
Pinia๋ ์ํคํ
์ฒ์ ์ผ๋ก ํ๋ซ ์คํ ์ด(flat store) ๊ตฌ์กฐ๋ฅผ ์ฑํํฉ๋๋ค. Vuex์ ๋ค์์คํ์ด์ค ๋ชจ๋์ ๊น์ ์ค์ฒฉ ๊ตฌ์กฐ๋ฅผ ํ์ฑํ๋ฉฐ, store.state.cart.items์ฒ๋ผ ์ ๊ทผ ๊ฒฝ๋ก๊ฐ ๊ธธ์ด์ง๊ณ ํ์
์ถ๋ก ์ด ๋ณต์กํด์ง๋๋ค. Pinia์์๋ ๊ฐ ์คํ ์ด๊ฐ ๋
๋ฆฝ์ ์ธ ์ปดํฌ์ ๋ธ์ด๋ฏ๋ก, ํ์ํ ์คํ ์ด๋ง ์ํฌํธํ์ฌ ์ฌ์ฉํฉ๋๋ค.
Vuex์์ Pinia๋ก์ ๋ง์ด๊ทธ๋ ์ด์ ์ ๋ต
๊ธฐ์กด Vuex ํ๋ก์ ํธ๋ฅผ Pinia๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ ๋๋ ๋ชจ๋ ๋จ์๋ก ์ ์ง์ ์ผ๋ก ์ ํํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค. Vuex์ Pinia๋ ๋์ผํ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๊ณต์กดํ ์ ์์ผ๋ฏ๋ก, ๋ฆฌ์คํฌ ์์ด ํ๋์ ๋ชจ๋์ฉ ๋ณํํ ์ ์์ต๋๋ค.
๋ค์์ Vuex ๋ชจ๋์ ์ ํ์ ์ธ ๊ตฌ์กฐ์ ๋๋ค.
// Before: Vuex module
// store/modules/user.ts
const userModule = {
namespaced: true,
state: () => ({
profile: null as UserProfile | null,
preferences: {} as UserPreferences,
}),
mutations: {
SET_PROFILE(state, profile: UserProfile) {
state.profile = profile
},
UPDATE_PREFERENCES(state, prefs: Partial<UserPreferences>) {
state.preferences = { ...state.preferences, ...prefs }
},
},
actions: {
async fetchProfile({ commit }) {
const profile = await api.getProfile()
commit('SET_PROFILE', profile)
},
async updatePreferences({ commit }, prefs: Partial<UserPreferences>) {
await api.updatePreferences(prefs)
commit('UPDATE_PREFERENCES', prefs)
},
},
getters: {
isLoggedIn: (state) => state.profile !== null,
displayName: (state) => state.profile?.name ?? 'Guest',
},
}์ด Vuex ๋ชจ๋์ Pinia์ Setup Store๋ก ๋ณํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
// After: Pinia store
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { api } from '@/lib/api'
export const useUserStore = defineStore('user', () => {
const profile = ref<UserProfile | null>(null)
const preferences = ref<UserPreferences>({})
const isLoggedIn = computed(() => profile.value !== null)
const displayName = computed(() => profile.value?.name ?? 'Guest')
async function fetchProfile() {
profile.value = await api.getProfile()
}
async function updatePreferences(prefs: Partial<UserPreferences>) {
await api.updatePreferences(prefs)
Object.assign(preferences.value, prefs)
}
return { profile, preferences, isLoggedIn, displayName, fetchProfile, updatePreferences }
})๋ง์ด๊ทธ๋ ์ด์
์์ ํต์ฌ์ ์ธ ๋ณ๊ฒฝ ์ฌํญ์ ์ธ ๊ฐ์ง์
๋๋ค. ์ฒซ์งธ, ๋ฎคํ
์ด์
์ด ์์ ํ ์ ๊ฑฐ๋ฉ๋๋ค. commit('SET_PROFILE', profile) ๋์ profile.value = await api.getProfile()์ผ๋ก ์ง์ ํ ๋นํฉ๋๋ค. ๋์งธ, state() ํจ์๊ฐ ref()๋ก, getters๊ฐ computed()๋ก, actions๊ฐ ์ผ๋ฐ ํจ์๋ก ๋์๋ฉ๋๋ค. ์
์งธ, ๋ค์์คํ์ด์ค ๋ฌธ์์ด('user/')์ด ์ ๊ฑฐ๋๊ณ , ์คํ ์ด ์๋ณ์ defineStore์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
์ปดํฌ๋ํธ ์ธก ์ฝ๋๋ ๊ฐ๊ฒฐํด์ง๋๋ค. mapState, mapActions, mapGetters ํฌํผ๊ฐ ๋ถํ์ํด์ง๋ฉฐ, useUserStore()๋ฅผ ํธ์ถํ์ฌ ๊ตฌ์กฐ ๋ถํด ์์ด ์ง์ ์ฌ์ฉํฉ๋๋ค.
SSR๊ณผ Nuxt 4์์์ Pinia ํ์ฉ
Pinia๋ SSR(์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง) ํ๊ฒฝ์์ ์ํ ํ์ด๋๋ ์ด์
์ ์๋์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค. Nuxt 4์์๋ @pinia/nuxt ๋ชจ๋์ ํตํด ์๋ฒ์์ ์์ฑ๋ ์คํ ์ด ์ํ๊ฐ ํด๋ผ์ด์ธํธ๋ก ์ ๋ฌ๋ฉ๋๋ค.
import { defineStore } from 'pinia'
export const useProductStore = defineStore('products', {
state: () => ({
items: [] as Product[],
selectedCategory: 'all',
}),
actions: {
async fetchProducts() {
// useFetch is Nuxt-specific โ use $fetch for store actions
this.items = await $fetch<Product[]>('/api/products', {
query: { category: this.selectedCategory },
})
},
},
getters: {
getById: (state) => (id: string) =>
state.items.find((item) => item.id === id),
filteredCount: (state) =>
state.items.filter((p) => p.inStock).length,
},
})Nuxt ํ๊ฒฝ์์ ์ฃผ์ํ ์ ์ ์คํ ์ด ์ก์
๋ด์์ useFetch ๋์ $fetch๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค๋ ๊ฒ์
๋๋ค. useFetch๋ ์ปดํฌ์ ๋ธ๋ก์ <script setup> ๋ด๋ถ์์๋ง ํธ์ถํด์ผ ํ๋ฉฐ, ์คํ ์ด ์ก์
์์ ์ฌ์ฉํ๋ฉด ์ปจํ
์คํธ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. $fetch๋ ๋ฒ์ฉ HTTP ํด๋ผ์ด์ธํธ๋ก ์คํ ์ด ๋ด๋ถ์์ ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
SSR์์ Pinia๋ ์์ฒญ๋ณ๋ก ๋ ๋ฆฝ๋ ์คํ ์ด ์ธ์คํด์ค๋ฅผ ์์ฑํ์ฌ ํฌ๋ก์ค ๋ฆฌํ์คํธ ์ํ ์ค์ผ์ ๋ฐฉ์งํฉ๋๋ค. Vuex์์๋ ์ด๋ฅผ ์ํด ์คํ ์ด ํฉํ ๋ฆฌ ํจํด์ ์๋์ผ๋ก ๊ตฌํํด์ผ ํ์ผ๋, Pinia์์๋ ๋ด์ฅ ๋ฉ์ปค๋์ฆ์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
2026๋ ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ ์ํ ๊ด๋ฆฌ ์ง๋ฌธ
Vue ์ํ๊ณ์ ์ํ ๊ด๋ฆฌ๋ ํ๋ฐํธ์๋ ๋ฉด์ ์์ ํต์ฌ ์ฃผ์ ์ ๋๋ค. ์๋๋ 2026๋ ๊ธฐ์ค์ผ๋ก ๋น์ถ๋๋ ์ง๋ฌธ๊ณผ ๋ต๋ณ ๋ฐฉํฅ์ ์ ๋ฆฌํ ๊ฒ์ ๋๋ค.
Q1: Pinia์์ ๋ฎคํ ์ด์ ์ด ์ ๊ฑฐ๋ ์ด์ ๋ ๋ฌด์์ ๋๊น?
Vuex์ ๋ฎคํ ์ด์ ์ DevTools ์ถ์ ๊ณผ ์ํ ๋ณ๊ฒฝ์ ๋ช ์์ฑ์ ์ํด ๋์ ๋์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ค๋ฌด์์๋ ๋๋ถ๋ถ์ ๋ฎคํ ์ด์ ์ด ๋จ์ํ setter์ ๋ถ๊ณผํ๊ณ , ๋ฎคํ ์ด์ -์ก์ ๊ฐ์ ๋ถํ์ํ ์ค๋ณต์ด ๋ฐ์ํ์ต๋๋ค. Pinia๋ Vue 3์ Proxy ๊ธฐ๋ฐ ๋ฐ์์ฑ ์์คํ ์ ํ์ฉํ์ฌ ์ง์ ์ํ ๋ณ๊ฒฝ์ DevTools์์ ์ถ์ ํ ์ ์์ผ๋ฏ๋ก, ๋ฎคํ ์ด์ ๋ ์ด์ด๊ฐ ๋ถํ์ํด์ก์ต๋๋ค.
Q2: Options Store์ Setup Store ์ค ์ด๋ ๊ฒ์ ์ ํํด์ผ ํฉ๋๊น?
Options Store๋ ๋ช
ํํ ๊ตฌ์กฐ ๋ถ๋ฆฌ(state, getters, actions)๊ฐ ํ์ํ ๊ฒฝ์ฐ์ ์ ํฉํฉ๋๋ค. Setup Store๋ ์ปดํฌ์ ๋ธ ํตํฉ, watch/watchEffect ์ฌ์ฉ, ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง ๊ตฌํ์ ์ ํฉํฉ๋๋ค. ๋์ผํ ํ๋ก์ ํธ์์ ๋ ๊ฐ์ง ๋ฐฉ์์ ํผ์ฉํ ์ ์์ผ๋ฉฐ, ์คํ ์ด์ ๋ณต์ก๋์ ๋ฐ๋ผ ์ ํํฉ๋๋ค.
Q3: Pinia์์ ์คํ ์ด ๊ฐ ์ํ ์์กด์ฑ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํฉ๋๊น?
์คํ ์ด A๊ฐ ์คํ ์ด B๋ฅผ, ์คํ ์ด B๊ฐ ์คํ ์ด A๋ฅผ ์ฐธ์กฐํ๋ ๊ฒฝ์ฐ, ์ปดํฌ์ ๋ธ ํธ์ถ์ ํจ์ ๋ด๋ถ๋ก ์ด๋์ํต๋๋ค. ์ต์์์์ useStoreA()๋ฅผ ํธ์ถํ๋ ๋์ , ํ์ํ ์์ ์ ํจ์ ๋ด๋ถ์์ ํธ์ถํ๋ฉด ์ํ ์ฐธ์กฐ๊ฐ ํด๊ฒฐ๋ฉ๋๋ค.
Q4: storeToRefs๋ ์ธ์ ์ฌ์ฉํฉ๋๊น?
์คํ ์ด์์ ์ํ์ getter๋ฅผ ๊ตฌ์กฐ ๋ถํดํ ๋ ๋ฐ์์ฑ์ ์ ์งํ๋ ค๋ฉด storeToRefs๊ฐ ํ์ํฉ๋๋ค. const { count } = useCounterStore()๋ก ๊ตฌ์กฐ ๋ถํดํ๋ฉด ๋ฐ์์ฑ์ด ๋์ด์ง๋๋ค. const { count } = storeToRefs(useCounterStore())๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ๋จ, ์ก์
์ storeToRefs์ ํฌํจ๋์ง ์์ผ๋ฏ๋ก ๋ณ๋๋ก ๊ตฌ์กฐ ๋ถํดํฉ๋๋ค.
Q5: Pinia์ ํ๋ฌ๊ทธ์ธ ์์คํ ์ ์ด๋ป๊ฒ ๋์ํฉ๋๊น?
Pinia ํ๋ฌ๊ทธ์ธ์ ๋ชจ๋ ์คํ ์ด์ ๊ณตํต ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์์ต๋๋ค. pinia.use()๋ก ๋ฑ๋กํ๋ฉฐ, ๊ฐ ์คํ ์ด๊ฐ ์์ฑ๋ ๋ ํ๋ฌ๊ทธ์ธ ํจ์๊ฐ ์คํ๋ฉ๋๋ค. ๋ก๊น
, ์์์ฑ(persistence), ์๋ฌ ์ฒ๋ฆฌ ๋ฑ์ ํฌ๋ก์ค์ปคํ
๊ด์ฌ์ฌ๋ฅผ ๊ตฌํํ๋ ๋ฐ ํ์ฉ๋ฉ๋๋ค.
Q6: SSR ํ๊ฒฝ์์ Pinia๊ฐ Vuex๋ณด๋ค ์ ๋ฆฌํ ์ ์ ๋ฌด์์ ๋๊น?
Pinia๋ ์์ฒญ๋ณ ์คํ ์ด ์ธ์คํด์ค ๊ฒฉ๋ฆฌ, ์๋ ์ํ ํ์ด๋๋ ์ด์
, ์ง๋ ฌํ ์์ ํ ์ํ ๊ด๋ฆฌ๋ฅผ ๋ด์ฅ์ผ๋ก ์ ๊ณตํฉ๋๋ค. Vuex์์๋ createStore ํฉํ ๋ฆฌ ํจํด, ์ํ ์ง๋ ฌํ/์ญ์ง๋ ฌํ ๋ก์ง, ํฌ๋ก์ค ๋ฆฌํ์คํธ ์ค์ผ ๋ฐฉ์ง๋ฅผ ์ํ ์๋ ์ค์ ์ด ํ์ํ์ต๋๋ค.
๋ฉด์ ์์๋ ๋จ์ํ API ์ฌ์ฉ๋ฒ๋ณด๋ค ์ค๊ณ ๊ฒฐ์ ์ ์ด์ ๋ฅผ ๋ฌป๋ ์ง๋ฌธ์ด ์ฆ๊ฐํ๊ณ ์์ต๋๋ค. "Pinia์์ ๋ฎคํ ์ด์ ์ด ์ ์ ๊ฑฐ๋์๋๊ฐ"์ฒ๋ผ ์ํคํ ์ฒ์ ๋ฐฐ๊ฒฝ์ ์ค๋ช ํ ์ ์์ด์ผ ํฉ๋๋ค. Vue ์ํ ๊ด๋ฆฌ ๋ฉด์ ์ค๋น์์ ๋ ๋ง์ ์ค์ ์ง๋ฌธ์ ํ์ธํ ์ ์์ต๋๋ค.
๊ฒฐ๋ก
- Pinia๋ Vue 3์ ๊ณต์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, Vuex 4๋ฅผ ์์ ํ ๋์ฒดํ๋ฉฐ ๋ฎคํ ์ด์ ์๋ ์ง๊ด์ API๋ฅผ ์ ๊ณต
- Options Store๋ ๊ตฌ์กฐ์ ๋ช ํ์ฑ, Setup Store๋ Composition API์์ ์์ ํ ํตํฉ ๋ฐ ์ปดํฌ์ ๋ธ ํ์ฉ์ ๊ฐ๊ฐ ์ ํฉ
- TypeScript ์ง์์์ Pinia๋ ์๋ ํ์ ์ฆ๊ฐ ์์ด ์์ ํ ํ์ ์ถ๋ก ์ ์ ๊ณตํ์ฌ, ๋๊ท๋ชจ ์ฝ๋๋ฒ ์ด์ค์์์ ๊ฐ๋ฐ ์์ฐ์ฑ์ ํฌ๊ฒ ํฅ์
- ํฌ๋ก์ค ์คํ ์ด ํต์ ์ ์ปดํฌ์ ๋ธ ํธ์ถ๋ก ์ฒ๋ฆฌ๋๋ฉฐ, Vuex์ ๋ค์์คํ์ด์ค ๊ธฐ๋ฐ ๋ฐฉ์ ๋๋น ๋ช ์์ ์ด๊ณ ํ์ ์์ ํจ
- Vuex์์ Pinia๋ก์ ๋ง์ด๊ทธ๋ ์ด์ ์ ๋ชจ๋ ๋จ์๋ก ์ ์ง์ ์ผ๋ก ์ํ ๊ฐ๋ฅํ๋ฉฐ, ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋์ผ ์ฑ์์ ๊ณต์กด ๊ฐ๋ฅ
- SSR ํ๊ฒฝ์์ Pinia๋ ์์ฒญ๋ณ ์ธ์คํด์ค ๊ฒฉ๋ฆฌ์ ์๋ ํ์ด๋๋ ์ด์ ์ ๋ด์ฅ ์ง์
- ๋ฒ๋ค ํฌ๊ธฐ(gzip ์ฝ 1KB), DevTools ํตํฉ, HMR ๋ด์ฅ ๋ฑ ์ค์ฉ์ ์ธก๋ฉด์์๋ Pinia๊ฐ ์ฐ์
Vue.js / Nuxt.js ๋ฉด์ ์ค๋น๊ฐ ๋์ จ๋์?
์ธํฐ๋ํฐ๋ธ ์๋ฎฌ๋ ์ดํฐ, flashcards, ๊ธฐ์ ํ ์คํธ๋ก ์ฐ์ตํ์ธ์.
ํ๊ทธ
๊ณต์
๊ด๋ จ ๊ธฐ์ฌ

Vue 3 ์ปดํฌ์ ๋ธ ์ฌํ ๊ฐ์ด๋: ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ํจํด๊ณผ ๊ธฐ์ ๋ฉด์ ์ง๋ฌธ 2026
Vue 3 ๊ณ ๊ธ ์ปดํฌ์ ๋ธ ํจํด์ ์ฒด๊ณ์ ์ผ๋ก ๋ถ์ํฉ๋๋ค. ๋น๋๊ธฐ ์ฒ๋ฆฌ, ์์กด์ฑ ์ฃผ์ , ํผ ์ ํจ์ฑ ๊ฒ์ฌ, ํ ์คํธ ์ ๋ต, ๊ทธ๋ฆฌ๊ณ 2026๋ ๊ธฐ์ ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ ์ง๋ฌธ๊ณผ ๋ต๋ณ์ ๋ค๋ฃน๋๋ค.

2026๋ Nuxt 4 ์๋ฒฝ ๊ฐ์ด๋: ์๋ก์ด ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ์ Nuxt 3 ๋ง์ด๊ทธ๋ ์ด์ ์ ๋ต
Nuxt 4์์ ๋์ ๋ app/ ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ, ์ฑ๊ธํค ๋ฐ์ดํฐ ํจ์นญ ๋ ์ด์ด, shallow reactivity, TypeScript ์ปจํ ์คํธ ๋ถ๋ฆฌ๋ฅผ ์ฝ๋ ์์ ์ ํจ๊ป ์์ธํ ๋ถ์ํฉ๋๋ค.

Vue.js ๋ฉด์ ํต์ฌ ์ง๋ฌธ: ํฉ๊ฒฉ์ ์ํ 25๋ฌธํญ
Vue.js ๋ฉด์ ์ ์ํ 25๊ฐ์ ํต์ฌ ์ง๋ฌธ. ๋ฐ์์ฑ๋ถํฐ composables๊น์ง, ๋ค์ ๋ฉด์ ์์ ๋น๋ ํต์ฌ ๊ฐ๋ ์ ์ ๋ฆฌํฉ๋๋ค.