실제 서비스 코드를 바로 Vue 3로 버전업 하기 시작하면
어디까지가 실수인지, 어디까지가 설계 변경인지 헷갈리고
빌드가 깨질 때 원인이 Vue 3 때문인지, 기존 버그 때문인지 구분하기 어렵습니다.
그래서 이번 글의 목표는 연습용으로 막 다뤄도 되는 Vue 2 + Router + Vuex 샘플 프로젝트 하나를 확보하는 것입니다.
이 샘플을 가지고 Vue 3 + compat build를 적용해보고
Router 3 → Router 4, Vuex → Pinia 변환을 실험해보고
실패/롤백을 마음 편히 반복할 수 있게 만드는 것이 핵심입니다.
샘플 프로젝트 구성 기준
샘플 프로젝트는 너무 복잡할 필요 없습니다.
대신 실제 프로젝트의 축소판처럼 구성하는 게 중요합니다.
추천 조건은 다음과 같습니다.
- Vue 2.x 사용 (2.6 이상이면 충분)
- vue-router 3.x 포함 (간단한 페이지 전환만 있어도 됨)
- Vue 3.x 또는 4.x 포함 (전역 state 1~2개 정도)
- axios 같은 HTTP 클라이언트가 하나 정도 들어 있으면 더 좋음
예를 들면 이런 기능이 있으면 충분합니다.
- /login, /dashboard 두 개 라우트
- auth Vuex 모듈 하나 (로그인 여부 저장)
- 대충 JSONPlaceholder 같은 API 호출 한 번
이 정도만 있어도 나중에 아래와 관련한 연습을 전부 해볼 수 있습니다.
- Router 3 → Router 4 마이그레이션
- Vuex → Pinia 마이그레이션
- 전역 플러그인/axios 설정 관리
연습을 전부 해볼 수 있습니다.
1. Vue CLI로 새 Vue 2 프로젝트 만들기
- 프로젝트 생성
만약 연습용 프로젝트가 없다면, Vue CLI로 간단히 하나 만드는 방법이 있습니다.
# Vue CLI 글로벌 설치 (없다면)
npm install -g @vue/cli
# Vue 2 기반 프로젝트 생성
vue create 프로젝트명
프리셋을 물어보면 아래와 같이 선택합니다.
- Manually select features
- Check: Babel, Router, Vuex, (원하면 Linter, Unit Testing)
- "Use history mode for router?" → 프로젝트 상황에 맞게 선택
- Vue 버전 선택 시 2.x 선택
생성이 끝나면 프로젝트 폴더로 이동해서 실행합니다.
cd 프로젝트명
npm run serve
브라우저에서 http://localhost:8080에 접속해 기본 템플릿이 뜨는지 확인합니다.
- 기본 폴더 구조
src/
main.js
App.vue
router
/index.js
store/
index.js # 루트 스토어
modules/
auth.js # 로그인 상태 관리
plugins/
axios.js # Axios 인스턴스 & Vue.prototype.$axios
views/
Home.vue
About.vue
Login.vue
Dashboard.vue
- main.js 예시
Vue 2 + Router 3 + Vuex 조합의 가장 기본적인 엔트리 코드 예시는 아래와 같습니다.
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './rotuer'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
- 여기서 new Vue({ ... }) 패턴이 나중에 Vue 3에서 createApp(App)으로 바뀌게 됨
- router, store는 각각 Router 3, Vuex 인스턴스를 import 해서 주입함
- Router 3 설정 예시
// src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
Vue.use(Router)
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/dashboard',
name: 'dashboard',
component: Dashboard,
meta: { requiresAuth: true }
}
]
})
// 간단한 인증 가드
router.beforeEach((to, from, next) => {
if (to.matched.some(r => r.meta.requiresAuth)) {
if (!store.getters['auth/isLoggedIn']) {
return next({ name: 'login' })
}
}
next()
})
export default router
- Vue.use(Router) → 나중에 Vue 3에서 app.use(router)로 바뀔 부분
- new Router({ mode, routes }) → Vue 3에서 createRouter({ history, routes })로 바뀔 부분
- Vuex 스토어 예시
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
increment({ commit }) {
commit('increment')
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
})
// src/store/modules/auth.js
const state = {
user: null,
token: null,
loading: false,
error: null
}
const mutations = {
LOGIN_REQUEST(state) {
state.loading = true
state.error = null
},
LOGIN_SUCCESS(state, { user, token }) {
state.loading = false
state.error = error
},
LOGOUT(state) {
state.user = null
state.token = null
}
}
const actions = {
async login({commit}, {email, password}) {
commit('LOGIN_REQUEST')
try {
const res = await this._vm.$axios.post('/login', { email, password })
const { user, token } = res.data
commit('LOGIN_SUCCESS', { user, token })
} catch (e) {
commit('LOGIN_FAILURE', '로그인에 실패했습니다.')
throw e
}
},
logout({ commit }) {
commit('LOGOUT')
}
}
const getters = {
isLoggedIn(state) {
return !!state.token
},
currentUser(state) {
return state.user
},
authError(state) {
return state.error
},
authLoading(state) {
return state.loading
}
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
- Vuex와 state / mutations / actions / getters를 다 한 번씩 써봅니다.
- 나중에 이 구조가 Pinia의 defineStore('counter', { state, actions, getters })로 대응될 예정
- Axios 플러그인
// src/plugins/axios.js
import axios from 'axios'
const instance = axios.create({
baseURL: 'https://example.com/api', // 나중에 .evn로 분리 가능
timeout: 5000
})
instance.interceptors.request.use(config => {
// 필요하다면 토큰 헤더에 붙이기
// const token = store.state.auth.token
// if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
instance.interceptors.response.use(
res => res,
error => {
// 공통 에러 처리 위치
return Promise.reject(error)
}
)
export default {
install(Vue) {
Vue.prototype.$axios = instance
}
}
- Vue 2 스타일 전역 플러그인 + Vue.prototype.$axios 패턴
- Vue 3 마이그레이션 시 app.config.globalProperties.$axios로 옮길 대표 예제가 됩니다.
- App.vue 및 화면 예시
라우터와 Vuex가 실제 동작하는지 확인하기 위해 아주 단순한 화면을 만듭니다.
<!-- src/App.vue -->
<template>
<div id="app">
<header>
<h1>Vue2 Migration Playground</h1>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/dashboard">Dashboard</router-link>
<router-link to="/login">Login</router-link>
</nav>
</header>
<main>
<router-view />
</main>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style scoped>
header {
margin-bottom: 16px;
}
nav a {
margin-right: 8px;
}
</style>
<!-- src/views/Home.vue -->
<template>
<section>
<h2>Home</h2>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">+1</button>
</section>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
...mapActions(['increment'])
}
}
</script>
<!-- src/views/About.vue -->
<template>
<section>
<h2>About</h2>
<p>이 프로젝트는 Vue 3 마이그레이션 연습용 Vue 2 샘플입니다.</p>
<button @click="increment">+1</button>
</section>
</template>
<script>
export default {
name: 'About'
}
</script>
<!-- src/views/Login.vue -->
<template>
<section class="login">
<h2>Login</h2>
<form @submit.prevent="onSubmit">
<div>
<label>Email</label>
<input v-model="email" type="email" required />
</div>
<div>
<label>Password</label>
<input v-model="password" type="password" required />
</div>
<p v-if="error" class="error">{{ error }}</p>
<button type="submit" :disabled="loading">
{{ loading ? '로그인 중...' : '로그인' }}
</button>
</form>
</section>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
name: 'Login',
data() {
return {
email: '',
password: ''
}
},
computed() {
...mapGetters('auth', ['authError', 'authLoading']),
error() {
return this.authError
},
loading() {
return this.authLoading
}
},
methods: {
...mapActions('auth', ['login']),
async onSubmit() {
try {
await this.login({
email: this.email,
password: this.password
})
this.$router.push({ name: 'dashboard' })
} catch (e) {
// 에러는 이미 store에 반영됨
}
}
}
}
</script>
<!-- src/views/Dashboard.vue -->
<template>
<section class="dashboard">
<h2>Dashboard</h2>
<p v-if="user">환영합니다. {{ user.name }}님</p>
<p v-else>로그인 정보가 없습니다.</p>
<button @click="onLogout">로그아웃</button>
</section>
</template>
<script>
import { mapGetters, mapActions } from 'vue'
export default {
name: 'Dashboard',
computed: {
...mapGetters('auth', ['currentUser']),
user() {
return this.currentUser
}
},
methods: {
...mapActions('auth', ['logout']),
onLogout() {
this.logout()
this.$router.push({ name: 'login' })
}
}
}
</script>
- /login에서 이메일/비밀번호 입력 → auth/login 액션 → Axios POST 호출
- 성공 시 /dashboard로 이동, 사용자 이름 표시
- 새로고침 시에는 실제 토큰 저장이 없으니 초기화되지만, 마이그레이션 연습용으로는 이 정도면 충분합니다.
2. 기존 샘플 프로젝트를 재사용할 때 체크할 것
이미 회사에서 쓰던 데모, 사이드 프로젝트, 강의용 레포가 있다면 그걸 써도 됩니다.
대신 아래 항목만 한 번식 확인합니다.
- pakage.json에 "vue": "^2.x"인지 확인
- vue-router, vuex 버전이 3.x 계열인지 확인
- npm install 또는 yarn 후 npm run serve / npm run dev가 정상 동작하는지
- .evn, 백엔드 API 주소 등 외부 의존성이 너무 무겁지 않은지
실행이 안 되거나 의존성이 꼬여 있으면 프로젝트를 새로 만드는 게 더 빠른 경우가 많습니다.
3. 빌드와 실행 상태 점검하기
샘플 프로젝트를 하나 골랐다면, 다음 세 가지를 반드시 체크해 두는 게 좋습니다.
1. 로컬 개발 서버 정상 구동
- npm run serve(혹은 npm run dev)가 에러 없이 돌아가는지
- 브라우저에서 라우팅이 정상 동작하는지
2. 프로덕션 빌드 가능 여부
- npm run build 실행이 잘 되는지
- dist 폴더가 정상 생성되는지
3. Git으로 스냅샷 남기기
- 아직 Vue 3 관련 변경을 하기 전 깨끗한 Vue 2 상태를 커밋/태그로 남겨두면 비교가 쉽습니다.
- 이 커밋이 마이그레이션 이전 기준점이 됩니다.
정리
- 연습용 Vue 2 + Router + Vuex 프로젝트 확보
- 로컬에서 빌드/실행이 잘 되는지 확인
- Git 기준점 커밋 생성
출처
'Framework > Vue' 카테고리의 다른 글
| Vue 2 컴포넌트를 Composition API로 바꾸는 연습 (0) | 2025.12.01 |
|---|---|
| Vue 3 Compat Build로 Vue 2 프로젝트 안전하게 버전업 (0) | 2025.12.01 |
| Vue 2에서 Vue 3 마이그레이션 시 참고해야 할 체크리스트 (0) | 2025.12.01 |
| Vue 3 라이프사이클과 인스턴스 API 변경 정리 (0) | 2025.11.28 |
| Vue 3에서 달라진 v-model과 템플릿 필터 제거 정리 (0) | 2025.11.28 |