본문 바로가기
framework_library/vue

⚙️ 어디에 어떤 설정을 넣어야 하는가? (App.vue, main.js, router 등)

by 죄니안죄니 2025. 5. 1.

⚙️ 어디에 어떤 설정을 넣어야 하는가? (App.vue, main.js, router 등)

Vue 기초 시리즈 > 3️⃣ Vue 작동 원리와 구조 이해 > 5️⃣ 어디에 어떤 설정을 넣어야 하는가?


✅ 개요: 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/index.js
 import { createRouter, createWebHistory } from 'vue-router'
 import Home from '../views/Home.vue'
 
 const routes = [
     { path: '/', name: 'Home', component: Home },
     { path: '/about', name: 'About', component: () => import('../views/About.vue') },
 ]
 
 const router = createRouter({
     history: createWebHistory(),
     routes
 })
 
 export default router
  • 라우트 목록과 라우팅 전략을 여기에 정의
  • router.beforeEach()를 통해 인증/가드 처리

1. 404 Not Found 페이지 처리

Vue Router에서는 정의되지 않은 경로로 접근 시 '404 페이지'로 라우팅할 수 있습니다.

// router/index.js
import NotFound from '@/views/NotFound.vue'

const routes = [
    { path: '/', component: Home },
    { path: '/login', component: Login },
    ...
    // 가장 마지막에 정의해야 작동합니다!
    { path: '/:pathMatch(.)', name: 'NotFound', component: NotFound }
]
  • :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()` 훅을 통해 전역 인증 가드를 설정할 수 있습니다.

// router/index.js
const router = createRouter({...})

router.beforeEach((to, from, next) => {
    const isLoggedIn = localStorage.getItem('token') !== null;


    if (to.meta.requiresAuth && !isLoggedIn) {
        next('/login'); // 로그인 안 했으면 로그인 페이지로
    } else {
        next(); // 통과
    }
})

 

// 라우터 설정 시 meta로 인증 여부 명시
{ path: '/mypage', component: MyPage, meta: { requiresAuth: true } }

핵심: 라우트마다 meta.requiresAuth 값을 체크하여 인증 여부를 판단합니다.

📌 로그인 체크는 meta.requiresAuth + beforeEach 조합


3. 역할(Role)에 따른 접근 제어

관리자/일반사용자 등 권한에 따라 접근을 다르게 하고 싶을 때는 이렇게 처리할 수 있습니다.

// meta에 roles 설정 추가
{
    path: '/admin',
        component: AdminPage,
            meta: { requiresAuth: true, roles: ['admin'] }
}

// beforeEach에서 roles 체크
router.beforeEach((to, from, next) => {
    const token = localStorage.getItem('token')
    const userRole = localStorage.getItem('role') // 예: 'admin', 'user'

    if (to.meta.requiresAuth && !token) {
        return next('/login')
    }

    if (to.meta.roles && !to.meta.roles.includes(userRole)) {
        return next('/403') // 권한 없음 페이지
    }

    next()
})

실전 팁: 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 페이지에 접근 시

  1. router.beforeEach()
    • authStore.isLoggedIn 확인
    • ❌ 로그인 안 되어 있으면 /login으로 리다이렉트
  2. ✅ 통과되면 Vue가 컴포넌트 렌더링
  3. 페이지 내에서 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 기초와 설정법으로 라우팅의 개념과 실전 적용을 배워봅니다.

댓글