⚙️ 어디에 어떤 설정을 넣어야 하는가? (App.vue, main.js, router 등)
Vue 기초 시리즈 > 3️⃣ Vue 작동 원리와 구조 이해 > 5️⃣ 어디에 어떤 설정을 넣어야 하는가?
- 1. 개요: Vue의 파일 구조 이해
- 2. main.js / main.ts의 역할
- 3. App.vue는 왜 항상 루트에 있을까?
- 4-1. router/index.js에 라우터 설정을 따로 분리하는 이유
- 4-2. router/index.js 라우터 파일 구성
- 5. store/index.js는 언제 사용하고 어떻게 나눌까?
- 6. 공통 설정/유틸은 어디에 모아야 할까?
- 🔚 마무리 및 실전 구성 팁
✅ 개요: Vue의 파일 구조 이해
Vue 프로젝트는 단순히 하나의 파일에서 시작하지 않습니다. 실제 동작은 main.js
또는 main.ts
에서 애플리케이션을 생성하고, 그 안에 App.vue
를 루트 컴포넌트로 마운트합니다. 여기에 라우터, 상태관리, 전역 컴포넌트 등을 연결하면서 '하나의 앱'이 됩니다.
✅ main.js의 역할
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
- createApp(App): 앱의 루트 컴포넌트 지정, <App /> 컴포넌트가 DOM의 #app 요소에 최초로 렌더링됩니다. 이후 모든 컴포넌트는 이 App.vue 아래로 중첩되어 들어갑니다.
- app.use(router): 라우터 기능 등록
- app.use(store): 상태 관리(Vuex/Pinia) 등록
- app.mount('#app'): 실제 DOM에 앱 렌더링
팁: main.js는 앱을 시작하는 '출입구'입니다. 최대한 설정만 하고 로직은 분리하세요.
✅ App.vue는 왜 항상 루트에 있을까?
App.vue는 모든 페이지를 감싸는 최상위 레이아웃입니다. DOM의 #app 요소에 최초로 렌더링되고, 여기에서 공통 헤더/푸터, router-view 등을 정의합니다.
<template>
<div id="app">
<Header />
<router-view />
<Footer />
</div>
</template>
실제 화면은 router-view에 따라 바뀌고, 고정된 영역은 App.vue에서 구성합니다.
즉, Vue 애플리케이션의 시작점이고,
<router-view>를 포함하여 모든 페이지의 라우트 출발점이 되고,
전체 앱의 레이아웃/틀을 관리하는 컨테이너 역할을 하고,
SPA 구조상 하나의 HTML (index.html) 파일로 떨어져서 자바스크립트에서 렌더링할 때 진입 컴포넌트로 삼기 때문입니다.
다른말로 index.html에서 설정하는 vue파일이 진입점. 루트 컴포넌트가 된다는 말입니다.
✅ router/index.js에 라우터 설정을 따로 분리하는 이유
라우터뷰는 루트컴포넌트 위에 실제로 접근하게되는 디자인입니다. 라우터 설정은 router/index.js
에 따로 분리하는 것이 일반적입니다.
1. 코드 구조 분리 (Separation of Concerns)
- main.js는 앱 초기화에 집중하고
- router/index.js는 라우팅 정의에만 집중함
- → 각 파일이 하나의 책임만 가지도록 분리하는 게 유지보수에 유리합니다.
2. 라우트가 많아질 때 대비
- 라우트가 10개, 20개 이상으로 늘어나면 main.js에서 관리하기엔 너무 길고 복잡해집니다.
- index.js에서 routes 배열, navigation guard, lazy loading 등을 효율적으로 관리할 수 있습니다.
3. Lazy Loading 및 코드 스플리팅 처리
{
path: '/about',
component: () => import('@/views/About.vue') // ✅ Lazy load
}
4. 라우터 가드, 전역 설정을 따로 넣기 좋음
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
next('/login')
} else {
next()
}
})
0. router/index.js 라우터 파일 - 기본
- 라우트 목록과 라우팅 전략을 여기에 정의
router.beforeEach()
를 통해 인증/가드 처리
1. 404 Not Found 페이지 처리
Vue Router에서는 정의되지 않은 경로로 접근 시 '404 페이지'로 라우팅할 수 있습니다.
- :pathMatch(.*)*는 모든 패턴을 catch하는 정규표현식입니다.
사용자가 /a/b/c 경로로 접근하면 $route.params.pathMatch // 👉 'a/b/c' ← 문자열! - '/:catchAll(.*)+' 는 URL 경로에서 슬래시(/)로 나뉜 부분들을 배열로 쪼개서 params에 넣어줍니다.
사용자가 /a/b/c 경로로 접근하면 $route.params.catchAll // 👉 ['a', 'b', 'c'] ← 배열 형태!
라우트 패턴 | $route.params 값 // $route.params.whatever 로 변수에 접근 |
/user/:id | { id: '123' } //변수이름 id |
/:pathMatch(.*) | { pathMatch: 'a/b/c' } //변수이름 pathMatch |
/:catchAll(.*)+ | { catchAll: ['a', 'b', 'c'] } //변수이름 catchAll |
📌 404 라우트는 반드시 다른 라우트 정의 뒤에 위치시켜야 합니다. (catch-all이므로)
2. 인증이 필요한 페이지 제한 (Navigation Guard)
Vue Router는 `beforeEach()` 훅을 통해 전역 인증 가드를 설정할 수 있습니다.
핵심: 라우트마다 meta.requiresAuth 값을 체크하여 인증 여부를 판단합니다.
📌 로그인 체크는 meta.requiresAuth + beforeEach 조합
3. 역할(Role)에 따른 접근 제어
관리자/일반사용자 등 권한에 따라 접근을 다르게 하고 싶을 때는 이렇게 처리할 수 있습니다.
실전 팁: roles 정보를 로그인 후 Vuex/Pinia에 저장해도 됩니다.
📌 권한 제어는 meta.roles + 사용자 role 정보 조합
📌 403, 404, 500 등 공통 에러 뷰는 따로 views/error 폴더에 분리
📌 라우팅 실패 시 next('/에러페이지')로 강제 이동
✅ store/index.js
Vuex 또는 Pinia 설정을 위한 중앙 저장소입니다.
// store/index.js import { createStore } from 'vuex'
export default createStore({
state: {
user: null
},
mutations: {
setUser(state, user) {
state.user = user
}
}
})
대형 프로젝트에서는 모듈을 나눠서 관리합니다.
인증(Auth)은 역할에 따라 axios, router, store가 "각자 해야 할 부분"이 있습니다.
즉, axios와 router는 각각 "다른 목적"의 인증 관련 처리를 합니다.
✅ 정리: 누가 무엇을 하는가?
Axios (인터셉터) | ✅ 토큰을 자동으로 붙이고, 인증 실패(401)를 감지함 | - 요청 헤더에 Authorization: Bearer xxx 자동 삽입 - 401 응답 오면 리프레시 토큰 재요청 or 로그아웃 유도 |
Vue Router (beforeEach 가드) | ✅ "이 페이지에 접근 가능한가?" 판단 | - 로그인 안 된 유저가 /admin 들어오면 /login으로 리다이렉트 - 특정 권한(role)에 따라 접근 제한 |
Store (예: Pinia) | ✅ 로그인/로그아웃 상태 관리 | - 로그인 여부 판단 (isAuthenticated) - 사용자 정보 (userInfo) 저장 - 토큰 저장 및 삭제 |
🧠 이해를 돕는 흐름 예시
👉 사용자가 /admin 페이지에 접근 시
- router.beforeEach()
- authStore.isLoggedIn 확인
- ❌ 로그인 안 되어 있으면 /login으로 리다이렉트
- ✅ 통과되면 Vue가 컴포넌트 렌더링
- 페이지 내에서 axios 요청 발생
- axios 인터셉터가 자동으로 토큰 붙임 (Authorization 헤더)
- 서버에서 401이 오면 → 리프레시 토큰 갱신 or authStore.logout()
✅ 정리 다시 한 번
- axios → 서버 통신 시 인증 처리
- router → 페이지 접근 제어
- store → 로그인 상태 보관, 갱신, 초기화
✅ 공통 설정/유틸은 어디에?
src/common/utils
: 유틸 함수src/common/axios
: 인터셉터, API 래퍼src/assets
: CSS, 이미지, 공통 스타일src/components/global
: 전역 등록 대상 컴포넌트
팁: "main.js에서 등록하고, 로직은 common 폴더에 분리"하는 것이 유지보수에 유리합니다.
📁 예시: src/common 디렉토리 구조
src/
├── common/
│ ├── axios/
│ │ ├── api.js # 실제 API 호출 함수 모음
│ │ └── interceptors.js # Axios 요청/응답 인터셉터 설정
│ ├── utils/
│ │ ├── date.js # 날짜 포맷 함수
│ │ ├── number.js # 숫자 포맷, 소수점 처리 등
│ │ └── validate.js # 공통 유효성 검사 함수
│ ├── constants.js # 상수 정의 (코드 목록 등)
│ ├── popup.js # 전역 팝업 핸들러
│ └── eventBus.js # mitt 또는 eventBus 객체
예시: api.js
// common/axios/api.js
import axios from 'axios'
export const getUser = (id) => axios.get(`/api/user/${id}`)
export const createUser = (data) => axios.post('/api/user', data)
예시: interceptors.js
// common/axios/interceptors.js
import axios from 'axios'
export default function setupInterceptors(mitt) {
axios.interceptors.response.use(
(res) => res,
(err) => {
if (err.response.status === 401) {
mitt.emit('unauthorized')
}
return Promise.reject(err)
}
)
}
예시: utils/date.js
export function formatDate(date) {
return new Date(date).toLocaleDateString('ko-KR')
}
📌 그리고 main.js에서는 이렇게 등록합니다:
import setupInterceptors from '@/common/axios/interceptors'
setupInterceptors(app.config.globalProperties.$emitter)
🔚 마무리 및 실전 구성 팁
- main.js는 단순하게 유지 (설정만)
- App.vue는 전체 앱의 뼈대
- router, store는 따로 index.js로 분리
- 공통 유틸/컴포넌트는 common 폴더에 집중
👉 다음 글: Vue Router 기초와 설정법으로 라우팅의 개념과 실전 적용을 배워봅니다.
'framework_library > vue' 카테고리의 다른 글
🚦 Vue Router – 동적 라우트 매칭과 파라미터 처리 (0) | 2025.05.05 |
---|---|
🚦 Vue Router 기초와 설정법 (0) | 2025.05.01 |
⚙️ Vue 이벤트 바인딩과 DOM 반응성 처리 구조 (0) | 2025.05.01 |
⚙️ Vue.js 렌더링 흐름 - 템플릿과 DOM 업데이트 구조 이해하기 (0) | 2025.05.01 |
⚙️ Vue 컴포넌트 라이프사이클 완전 정복 (0) | 2025.05.01 |
댓글