Vue 3 Pinia vs Vuex ์™„๋ฒฝ ๋น„๊ต: 2026๋…„ ์ƒํƒœ ๊ด€๋ฆฌ ์ „๋žต๊ณผ ๋ฉด์ ‘ ํ•ต์‹ฌ ์งˆ๋ฌธ

Vue 3 ์ƒํƒœ๊ณ„์—์„œ Pinia์™€ Vuex๋ฅผ ๋น„๊ต ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. Options Store์™€ Setup Store ํŒจํ„ด, TypeScript ํ†ตํ•ฉ, ํฌ๋กœ์Šค ์Šคํ† ์–ด ๊ตฌ์„ฑ, SSR ์ง€์›, Vuex์—์„œ Pinia๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ „๋žต, ๊ทธ๋ฆฌ๊ณ  2026๋…„ ๋ฉด์ ‘์—์„œ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” ์ƒํƒœ ๊ด€๋ฆฌ ์งˆ๋ฌธ์„ ์ฝ”๋“œ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Vue 3 Pinia vs Vuex state management comparison

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๋ฅผ ํ†ตํ•ด ์ƒํƒœ๋ฅผ ์ง์ ‘ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

stores/counter-options.tstypescript
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์— ์ต์ˆ™ํ•œ ํŒ€์—์„œ๋Š” ํ•™์Šต ๋น„์šฉ์ด ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค.

stores/counter-setup.tstypescript
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)์ด ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค.

typescript
// 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.count

Vuex์—์„œ๋Š” commit('SET_COUNT', value) ํ˜ธ์ถœ ์‹œ ๋ฎคํ…Œ์ด์…˜ ์ด๋ฆ„์ด ๋ฌธ์ž์—ด์ด๋ฏ€๋กœ, ์˜คํƒ€๊ฐ€ ์žˆ์–ด๋„ ์ปดํŒŒ์ผ ํƒ€์ž„์— ์žกํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ํ™•๋ณดํ•˜๋ ค๋ฉด ๋ณ„๋„์˜ ํ—ฌํผ ํ•จ์ˆ˜๋‚˜ ํƒ€์ž… ๊ฐ€๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Pinia์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ์™„์ „ํžˆ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

typescript
// 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๋ฅผ ๊ฒฐํ•ฉํ•œ ๊ฒ€์ƒ‰ ์Šคํ† ์–ด์ž…๋‹ˆ๋‹ค.

stores/search.tstypescript
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 }) ๊ฐ™์€ ๋ณต์žกํ•œ ํŒจํ„ด ๋Œ€์‹ , ๋‹ค๋ฅธ ์Šคํ† ์–ด์˜ ์ปดํฌ์ €๋ธ”์„ ์ง์ ‘ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

stores/cart.tstypescript
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 ๋ชจ๋“ˆ์˜ ์ „ํ˜•์ ์ธ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

typescript
// 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๋กœ ๋ณ€ํ™˜ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

typescript
// 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 ๋ชจ๋“ˆ์„ ํ†ตํ•ด ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ ์Šคํ† ์–ด ์ƒํƒœ๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.

stores/products.ts โ€” SSR-safe Pinia store for Nuxt 4typescript
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
#pinia
#vuex
#state-management
#interview

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

Vue 3 ๊ณ ๊ธ‰ ์ปดํฌ์ €๋ธ” ํŒจํ„ด์˜ ํ•ฉ์„ฑ ๋ฐ ์˜์กด์„ฑ ์ฃผ์ž… ๋‹ค์ด์–ด๊ทธ๋žจ

Vue 3 ์ปดํฌ์ €๋ธ” ์‹ฌํ™” ๊ฐ€์ด๋“œ: ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํŒจํ„ด๊ณผ ๊ธฐ์ˆ  ๋ฉด์ ‘ ์งˆ๋ฌธ 2026

Vue 3 ๊ณ ๊ธ‰ ์ปดํฌ์ €๋ธ” ํŒจํ„ด์„ ์ฒด๊ณ„์ ์œผ๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ, ์˜์กด์„ฑ ์ฃผ์ž…, ํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ํ…Œ์ŠคํŠธ ์ „๋žต, ๊ทธ๋ฆฌ๊ณ  2026๋…„ ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” ์งˆ๋ฌธ๊ณผ ๋‹ต๋ณ€์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

Nuxt 4 directory structure and migration guide

2026๋…„ Nuxt 4 ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ์ƒˆ๋กœ์šด ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ์กฐ์™€ Nuxt 3 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ „๋žต

Nuxt 4์—์„œ ๋„์ž…๋œ app/ ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ์กฐ, ์‹ฑ๊ธ€ํ†ค ๋ฐ์ดํ„ฐ ํŒจ์นญ ๋ ˆ์ด์–ด, shallow reactivity, TypeScript ์ปจํ…์ŠคํŠธ ๋ถ„๋ฆฌ๋ฅผ ์ฝ”๋“œ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ์ƒ์„ธํžˆ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.

์ฝ”๋“œ ๋ธ”๋ก๊ณผ Vue ๋กœ๊ณ ๊ฐ€ ๋“ฑ์žฅํ•˜๋Š” Vue.js ๊ธฐ์ˆ  ๋ฉด์ ‘ ์ผ๋Ÿฌ์ŠคํŠธ

Vue.js ๋ฉด์ ‘ ํ•ต์‹ฌ ์งˆ๋ฌธ: ํ•ฉ๊ฒฉ์„ ์œ„ํ•œ 25๋ฌธํ•ญ

Vue.js ๋ฉด์ ‘์„ ์œ„ํ•œ 25๊ฐœ์˜ ํ•ต์‹ฌ ์งˆ๋ฌธ. ๋ฐ˜์‘์„ฑ๋ถ€ํ„ฐ composables๊นŒ์ง€, ๋‹ค์Œ ๋ฉด์ ‘์—์„œ ๋น›๋‚  ํ•ต์‹ฌ ๊ฐœ๋…์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.