1. SPA에서 라우터가 필요한 이유
Vue로 간단한 컴포넌트까지만 만들 때는 하나의 페이지에서만 작업해도 충분하지만,
실제 서비스에서는 다음과 같은 화면이 필요해집니다.
- /login: 로그인 페이지
- /users: 유저 목록 페이지
- /users/1: 특정 유저 상세 페이지
이때마다 HTML 파일을 새로 요청하면 전체 페이지가 새로고침되기 때문에, 사용자 경험이 끊기는 느낌이 강합니다.
Vue에서는 이런 문제를 해결하기 위해 SPA(Single Page Application) 방식과 함께 Vue Router를 사용합니다.
Vue Router는 Vue 공식 라우팅 라이브러리로,
- URL과 컴포넌트(화면)를 매핑
- <router-view> 위치에 현재 URL에 해당하는 컴포넌트를 렌더링
- 페이지 전체를 다시 로드하지 않고도 클라이언트 사이드에서 화면을 전환
할 수 있게 해줍니다.
2. Vue Router의 기본 구성 요소
Vue Router를 사용할 때 핵심이 되는 개념은 세 가지입니다.
1) routes 배열: path와 component를 매핑한 설정
2) <router-view>: 현재 경로에 해당하는 컴포넌트를 실제로 렌더링하는 영역
3) <router-link>: 페이지 새로고침 없이 라우트 간 이동할 수 있는 링크 컴포넌트
Vue 2 + Vue Router 3 기준으로 가장 기본적인 설정은 아래와 같습니다.
<div id="app">
<h1>간단한 Vue Router 예제</h1>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<router-view></router-view>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@3/dist/vue-router.js"></script>
<script>
const Home = { template: '<div>Home 페이지</div>' }
const About = { template: '<div>About 페이지</div>' }
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
]
const router = new VueRouter({
mode: 'history', // 또는 'hash'
routes
})
new Vew({
el: '#app',
router
})
</script>
과정을 정리하자면 아래와 같습니다.
1) routes 배열에서 URL과 컴포넌트를 매핑합니다.
2) new VueRouter({ routes })로 라우터 인스턴스를 생성합니다.
3) new Vue({ router })로 루트 인스턴스에 라우터를 주입합니다.
4) <router-link> 리턴값은 기본적으로 <a> 태그로 렌더링되면서, 브라우저의 전체 새로고침 없이 URL과 화면을 변경합니다.
3. 동적 라우트와 파라미터: /users/:id 패턴
실제 서비스에서는 유저 상세, 게시글 상세처럼 URL에 ID를 포함하는 패턴이 자주 등장합니다.
/users/1
/users/2
/posts/10
Vue Router에서는 이런 URL을 위해 동적 세그먼트(dynamic segment)를 제공합니다.
클론(:)으로 시작하는 부분이 파라미터입니다.
[ 예시 ]
const UserDetail = {
template: '<div>User ID: {{ $route.param.id }}</div>'
}
const routes = [
{path: '/users/:id', component: UserDetail}
]
- path: '/users/:id'에서 :id가 동적파라미터
- URL이 /users/10일 경우 this.$route.params.id === '10'
동적 파라미터를 여러 개 사용하는 것도 가능합니다.
{ path: '/users/:userId/posts/:postId', component: PostDetail }
이때 this.$route.params에는 다음과 같은 객체가 들어갑니다.
// /users/7/posts/123
$route.params // { userId: '7', postId: '123' }
공식 문서에서도 이 방식을 동적 라우트 매칭(dynamic route matching)이라고 부르며,
URL 기반 상세 페이지 구현의 기본 패턴으로 소개합니다.
4. 쿼리 스트링과 프로그래매틱 내비게이션
파라미터 외에도 ?page=2&keyword=vue 같은 쿼리 스트링을 검색 조건이나 페이징 정보를 넘기는 경우도 많습니다.
Vue Router에서는 라우터 인스턴스의 메서드를 통해 코드에서 직접 라우팅을 제어할 수 있습니다.
- 예시
// 검색 버튼 클릭 시 호출
this.$router.push({
path: '/search',
query: {
keyword: this.keyword,
page: 1
}
})
검색 결과 페이지에서는 아래처럼 사용할 수 있습니다.
export default {
computed: {
keyword() {
return this.$route.query.keyword
},
page() {
return Number(this.$route.query.page || 1)
}
}
}
공식 문서에서도 route.params, route.query를 통해 파라미터와 쿼리 스트링을 읽어올 수 있다고 정리하고 있습니다.
5. 네비게이션 가드로 인증 처리의 기초 잡기
실제 서비스라면 로그인 하지 않은 사용자는 /login 경로로 보내기 같은 인증 처리가 거의 필수입니다.
이때 사용하는 것이 네비게이션 가드(Navigation Guards)입니다.
네비게이션 가드는 라우팅이 일어나기 전/후에 개입해서
접근을 막거나(취소) 다른 페이지로 리다이렉트 할 수 있는 훅(hook)입니다.
네비게이션 가드는 전역 가드, 라우트 별 가드, 컴포넌트 내부 가드 세 유형으로 나뉩니다.
1️⃣ 전역가드
1) beforeEach
가장 많이 쓰는 패턴은 전역 beforeEach 가드입니다.
router.beforeEach를 사용하여 전역 before 가드를 등록할 수 있습니다.
전역 before 가드는 생성 순서대로, 내비게이션이 트리거될 때마다 호출됩니다.
가드는 비동기적으로 해결될 수 있으며, 모든 훅이 해결되기 전까지 네비게이션은 보류 상태로 간주됩니다.
const router = new VueRouter({ routes })
router.beforeEach((to, from, next) => {
const isLoggedIn = !!localStorage.getItem('token')
if (to.meta.requiresAuth && !isLoggedIn) {
// 인증이 필요한 페이지인데 로그인이 안 되어 있으면
next('/login')
} else {
next() // 그대로 진행
}
})
- to 라우트: 대상 Route 객체로 이동
- from 라우트: 현재 라우트로 오기 전 라우트
- next 함수
- next(): 파이프라인의 다음 훅으로 이동. 훅이 없는 경우 네비게이션은 승인됨
- next(false): 현재 네비게이션을 종료함. 브라우저 URL이 변경되면(사용자 또는 뒤로 버튼을 통해 수동으로 변경) from 경로의 URL로 재설정됨
- next('/') 또는 next({ path: '/' }): 다른 위치로 리디렉션함. 현재 네비게이션이 중단되고 새 네비게이션이 시작됨
- next(error): next에 전달된 인자가 Error의 인스턴스이면 탐색이 중단되고 router.onError()를 이용해 등록된 콜백에 에러가 전달됨 (2.4.0 이후 추가)
next 함수를 호출하지 않을 경우 훅이 제대로 불러지지 않습니다.
2) beforeResovle
2.5.0 이후로 router.beforeResolve를 사용하여 글로벌 가드를 등록할 수 있습니다.
beforeEach와 유사하게 모든 네비게이션에서 트리거되지만,
네비게이션이 확정되기 직전, 모든 컴포넌트 내 가드와 비동기 라우트 컴포넌트가 해결된 후에 호출된다는 차이가 있습니다.
router.beforeResolve는 사용자가 페이지에 진입할 수 없는 경우
데이터를 가져오거나 기타 작업을 피하고 싶을 때 이상적입니다.
3) beforeAfter
전역 after 훅도 등록할 수 있지만, 가드와 달리 이 훅들은 next 함수를 받지 않으며 네비게이션에 영향을 줄 수 없습니다.
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})
이 훅은 분석, 페이지 제목 변경, 페이지 알림과 같은 접근성 기능 등 다양한 용도로 유용합니다.
2️⃣ 라우트 별 가드
beforeEnter 가드를 라우트 설정 객체에 직접 정의할 수 있습니다.
const router = new VueRouter({
routes: [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// 네비게이션 거부
return false
}
}
]
})
beforeEnter 가드는 해당 라우트에 진입할 때만 트리거되며,
params, query 또는 hash가 변경될 때에는 트리거되지 않습니다.
(예 - /users/2에서 /users/3로 이동하거나 /users/2#info에서 /users/2#projects로 이동)
오직 다른 라우트에서 진입할 때만 트리거됩니다.
beforeEnter에 함수 배열을 전달할 수도 있습니다.
여러 라우트에서 가드를 재사용할 때 함수 배열을 전달하는 게 유용합니다.
function removeQueryParams(to) {
if (Object.keys(to.query).length)
return { path: to.path, query: {}, hash: to.hash }
}
function removeHash(to) {
if (to.hash) return { path: to.path, query: to.query, hash: '' }
}
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: [removeQueryParams, removeHash],
},
{
path: '/about',
component: UserDetails,
beforeEnter: [removeQueryParams],
},
]
중첩 라우트를 사용할 때, 부모와 자식 모두 beforeEnter를 사용할 수 있습니다.
부모 라우트에 배치하면, 동일한 부모를 가진 자식 간 이동 시에는 트리거되지 않습니다.
const routes = [
{
path: '/user',
beforeEnter() {
// ...
},
children: [
{ path: 'list', component: UserList },
{ path: 'details', component: UserDetails },
],
},
]
위 예시에서 /user/list와 /user/details 간 이동 시에는
두 라우트가 동일한 부모를 공유하기 때문에 beforeEnter가 호출되지 않습니다.
만약 beforeEnter 가드를 details 라우트에 배치하면,
두 라우트 간 이동 시 beforeEnter가 호출됩니다.
라우트 meta 필드와 전역 내비게이션 가드를 사용하여 라우트별 가드와 유사한 동작을 구현할 수 있습니다.
이 내용은 컴포넌트 내 가드 아래에 작성하겠습니다.
3️⃣ 컴포넌트 내부 가드
beforeRouteEnter와 beforeRouteLeave를 사용하여 라우트 컴포넌트(라우터 설정으로 전달되는 컴포넌트) 안에 라우트 네비게이션 가드를 직접 정의할 수 있습니다.
Options API 사용시 (Vue 2) 다음 옵션을 라우트 컴포넌트에 추가할 수 있습니다.
- beforeRouteEnter
- beforeRouteUpdate (2.2 버전에 추가)
- beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 이 컴포넌트를 렌더링하는 라우트 앞에 호출됩니다.
// 이 가드가 호출 될 때 아직 생성되지 않았기 때문에
// 'this' 컴포넌트 인스턴스에 접근할 수 없습니다.
},
beforeRouteLeave(to, from) {
// 이 컴포넌트를 렌더링하는 라우트가 이전으로 네비게이션 될 때 호출됩니다.
// 'this' 컴포넌트 인스턴스에 접근할 수 있습니다.
}
}
beforeRouteEnter 가드는 네비게이션이 확인되기 전에 가드가 호출되어서
새로운 엔트리 컴포넌트가 아직 생성되지 않았기 때문에 this에 접근하지 못합니다.
그러나 콜백을 next에 전달하여 인스턴스에 액세스 할 수 있습니다.
네비게이션이 확인되고 컴포넌트 인스턴스가 콜백에 전달인자로 전달될 때 콜백이 호출됩니다.
beforeRouteEnter (to, from, next) {
next(vm => {
// `vm`을 통한 컴포넌트 인스턴스 접근
})
}
beforeRouteEnter만이 next에 콜백을 전달하는 것을 지원합니다.
beforeRouteUpdate와 beforeRouteLeave에서는 이미 this가 사용 가능하므로,
콜백 전달이 불필요하며 지원되지 않습니다.
beforeRouteUpdate (to, from) {
// 그냥 `this`를 사용하세요
this.name = to.params.name
}
leave 가드는 주로 사용자가 저장하지 않은 편집 내용을 가진 채로 라우트를 떠나는 것을 방지하는 데 사용됩니다.
false를 반환하여 네비게이션을 취소할 수 있습니다.
beforeRouteLeave (to, from) {
const answer = window.confirm('정말로 떠나시겠습니까? 저장되지 않은 변경사항이 있습니다!')
if (!answer) return false
}
- 라우트 meta 필드와 내비게이션 가드 사용
때때로 라우트에 임의의 정보를 추가하고 싶을 때가 있습니다.
예를 들어 트랜지션 이름이나, 라우트에 접근할 수 있는 역할 등입니다.
이는 meta 속성을 이용하면 되고, 이 속성은 여러 프로퍼티를 가진 객체를 받아
라우트 위치와 네비게이션 가드에서 접근할 수 있습니다.
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 인증된 사용자만 게시글을 작성할 수 있음
meta: { requiresAuth: true },
},
{
path: ':id',
component: PostsDetail,
// 누구나 게시글을 읽을 수 있음
meta: { requiresAuth: false },
},
],
},
]
그렇다면, 이 meta 필드는 어떻게 접근할 수 있을까요?
routes 설정에 있는 각 라우트 객체를 라우트 레코드라고 부르는데, 이는 중첩될 수 있습니다.
예를 들어, /posts/new는 부모 라우트 레코드(path: '/posts')와 자식 라우트 레코드(path: '/new') 모두와 매칭됩니다.
라우트에 의해 매칭된 모든 라우트 레코드는 route 객체(그리고 네비게이션 가드의 라우트 객체)에서 route.matched 배열로 노출됩니다.
이 배열을 순회하여 모든 meta 필드를 확인할 수도 있지만,
Vue Router는 부모에서 자식까지 모든 meta 필드를 비재귀적으로 병합한 route.meta도 제공합니다.
즉, 아래와 같이 간단히 사용할 수 있습니다.
router.beforeEach((to, from) => {
// 모든 라우트 레코드를 확인하는 대신
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
// 이 라우트는 인증이 필요하므로, 로그인 여부를 확인
// 로그인하지 않았다면 로그인 페이지로 리다이렉트
return {
path: '/login',
// 나중에 돌아올 수 있도록 현재 위치 저장
query: { redirect: to.fullPath },
}
}
})
- 전체 네비게이션 해결 흐름
- 네비게이션 트리거
- 비활성화된 컴포넌트의 beforeRouteLeave 가드 호출
- 전역 beforeEach 가드 호출
- 재사용되는 컴포넌트의 beforeRouteUpdate 가드 호출
- 라우트 설정의 beforeEnter 호출
- 비동기 라우트 컴포넌트 해결
- 활성화된 컴포넌트의 beforeRouteEnter 호출
- 전역 beforeResolve 가드 호출
- 네비게이션 확정
- 전역 afterEach 훅 호출
- DOM 업데이트 트리거
- 인스턴스화된 인스턴스와 함께 beforeRouteEnter 가드에 전달된 콜백 호출
정리
- Vue Router는 Vue 공식 라우팅 라이브러리로, SPA에서 URL과 화면을 연결하는 역할을 한다.
- 기본 구성 요소
- routes 배열
- <router-view>
- <router-link>
- 동적 라우트(/users/:id)와 this.$route.params, this.$route.query로 파라미터/쿼리 읽기
- 네비게이션 가드
- 전역 가드
- 전역 Before 가드: router.beforeEach
- 전역 Resolve 가드: router.beforeResolve
- 전역 After 훅: router.afterEach
- ㅈㅇ
- 라우트 별 가드
- router.beforeEnter
- meta 태그와 전역 가드를 함께 사용하면 비슷함
- 컴포넌트 내 가드
- router.beforeRouteEnter
- router.beforeRouteUpdate
- router.beforeRouteLeave
- 전역 가드
출처
Vue Router3 공식 가이드 - 라우트 메타 필드
Vue Router4 공식 가이드 - 라우트 메타 필드
'Framework > Vue' 카테고리의 다른 글
| Vuex 기초: Vue 전역 상태 관리 (state, getters, mutations, actions, modules) (1) | 2025.11.20 |
|---|---|
| Vue Router와 Axios 활용해 라우팅 + API 연동으로 SPA 만들기 (0) | 2025.11.20 |
| Vue.js SFC, props, emit으로 부모-자식 데이터 흐름 잡기 (0) | 2025.11.19 |
| Vue2 Options API : data, methods, computed, watch (0) | 2025.11.19 |
| Vue.js 입문 : 기본 철학과 기본 문법 정리 (0) | 2025.11.18 |