Vue Router
Vue Router는 SPA and CSR 위에서 동작하는 client-side router로, browser URL을 Vue.js component tree에 mapping한다. Route map, dynamic/named route, programmatic navigation, nested route, route props, lazy loading, navigation guard라는 하위 메커니즘을 가지며, 이 페이지가 답하는 질문은 URL state와 rendered component hierarchy를 어떻게 일치시키는가다.
Route 매칭과 Navigation 흐름
- Route record는 path/name, rendered component, child route, meta와 guard 같은 navigation rule을 정의한다.[1]
- RouterLink는 navigation intent를 만들고 RouterView는 matched component가 배치되는 outlet이다.[1:1]
- Router source는 RouterLink/RouterView, dynamic route, named route, programmatic navigation, nested route, route props, lazy loading, navigation guard를 순서대로 다룬다.[1:2]
- Named route navigation은 path string을 직접 조합하는 대신
{ name, params }로 target을 표현한다.[1:3] - Nested route는
children으로 URL hierarchy와 component outlet hierarchy를 구성한다.[1:4] - Dynamic import를 route component에 사용하면 최초 app load가 아니라 해당 route 방문 시점에 code를 load할 수 있다.[1:5]
flowchart LR URL[Browser URL] --> Match[Route records match] Match --> Guard[Guard pipeline] Guard -->|approved| Load[Load route component] Load --> View[RouterView renders] Guard -->|redirected or cancelled| URL
Router가 URL을 application state의 외부 표현으로 다루는 이유
Router는 단순 menu 전환 도구가 아니라 URL을 application state의 외부 표현으로 유지하는 경계라고 볼 수 있다. URL parameter는 share/bookmark/back-forward navigation이 필요한 state에 적합하고, component local state나 global store를 모두 대체하지는 않는다. Route name은 path structure가 바뀌어도 caller가 의도 중심 navigation을 유지하게 해준다.
Navigation guard: policy hook
Guard는 인증·권한·이탈 확인 같은 navigation policy에 사용하되 data fetching 전체를 하나의 global guard에 몰지 않는 편이 낫다 — 모든 data loading과 authorization logic을 global guard 하나에 집중하는 것은 흔한 실패 모드다.
- Navigation guard는 global, per-route, in-component 수준에서 등록할 수 있으며 redirect하거나 cancel하는 policy hook이다.[2]
beforeEach같은 guard에서false를 return하면 현재 navigation을 취소하고 URL을 이전 상태로 되돌린다. route location을 return하면 그 navigation을 redirect한다.[2:1]- global
beforeEachguard는 생성 순서대로 호출되고, guard 종류 사이의 호출 순서는 Full Navigation Resolution Flow를 따른다.[2:2]nextcallback style은 아직 지원되지만, 쓰는 경우 guard를 한 번 통과하는 동안next를 정확히 한 번 호출해야 한다.[2:3] broad matcher의 policy와 route-specific policy가 겹칠 때 책임과 순서를 명확히 두고,next와 return style을 섞어 흐름을 이중으로 제어하지 말라는 것은 이 page의 해석이다.
주요 구성 요소
| 요소 | 역할 | 주의점 |
|---|---|---|
routes |
URL pattern과 component를 mapping | Route order와 dynamic segment 범위를 확인한다. |
RouterLink |
Client navigation link 생성 | 단순 click handler보다 link semantics를 보존한다. |
RouterView |
Matched component outlet | Nested route마다 필요한 outlet을 둔다. |
useRoute() |
현재 route state 읽기 | Route object 전체보다 필요한 param/query를 관찰한다. |
useRouter() |
Programmatic navigation 실행 | User-facing link는 RouterLink를 우선한다. |
동적・이름 기반 navigation
- Dynamic segment:
/users/:id는 여러 user URL을 하나의 route record로 처리한다. - Named route:
router.push({ name: 'user', params: { id } })는 path encoding과 조합 책임을 router에 맡긴다. Path string을 여러 caller에서 직접 조합하면 route가 바뀔 때 수정 지점이 늘어나므로, named route가 이런 변경에 더 강하다. - Path와
params를 같은 navigation object에 넣으면 params가 무시될 수 있으므로 name+params 또는 완성된 path 중 하나를 선택한다.
Nested route와 route props
- Parent layout 안의 RouterView가 child route component를 렌더링한다. 이 outlet 배치는 Vue Component Architecture가 설명하는 component hierarchy 위에서 이뤄지며, nested route를 만들고도 parent component에 RouterView를 두지 않으면 child가 렌더링되지 않는다.
childrenpath가/로 시작하지 않으면 parent path에 이어지는 상대 path가 된다.- Component가
$route에 직접 결합되는 범위를 줄이려면 route param을 Props로 전달해 component input contract로 바꿀 수 있다. Route param이 바뀌어 component가 reuse되는데 mount hook만 의존해 data를 갱신하지 않는 것도 이 재사용 동작에서 놓치기 쉬운 지점이다.
Lazy loading
const routes = [
{
path: '/about',
component: () => import('./views/AboutView.vue'),
},
]
- Route 방문 시 chunk를 load하므로 initial bundle을 줄일 수 있다. 반대로 route component를 모두 eager import하면 initial bundle을 불필요하게 키우게 된다.
- 지나치게 작은 chunk 분리는 network overhead와 loading state 복잡도를 늘릴 수 있다.
Navigation guard flow
Source p.52의 흐름을 재사용 가능한 순서로 단순화하면 다음과 같다.[1:6]
- Navigation이 trigger된다.
- Leaving component guard와 global
beforeEach가 실행된다. - Reused component update guard와 route
beforeEnter가 실행된다. - Async route component를 resolve하고 entering component guard를 실행한다.
- Global
beforeResolve후 navigation을 confirm한다. - Global
afterEach와 DOM update가 이어진다.
경계
- Router는 URL과 view hierarchy를 관리한다. 모든 application state를 route query/params에 넣는 도구가 아니다.
- 인증 여부의 source of truth는 auth state이며 guard는 그 state를 읽어 navigation policy를 적용한다.
- Server가 매 navigation마다 새 HTML document를 반환하는 MPA routing과 달리, Router는 URL과 view hierarchy를 client에서 관리한다.
- Server는 history mode URL을 app entry로 fallback하도록 배포 설정을 제공해야 하지만, 이 source는 server 설정을 다루지 않는다.
예시
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/users/:id',
name: 'user',
component: () => import('./views/UserView.vue'),
props: true,
children: [
{ path: 'settings', name: 'user-settings', component: () => import('./views/UserSettings.vue') },
],
},
]
const router = createRouter({ history: createWebHistory(), routes })
router.beforeEach((to) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
return { name: 'login', query: { redirect: to.fullPath } }
}
})
관련
- SPA and CSR: client-side navigation이 필요한 실행 맥락이다.
- Vue.js: route component가 실행되는 framework model이다.
- Vue Component Architecture: nested route가 배치하는 component hierarchy를 설명한다.
출처
테스트 질문
- Named route가 path string 직접 조합보다 변경에 강한 이유는 무엇인가?
- Nested route에서 parent RouterView가 필요한 이유는 무엇인가?
- Route lazy loading과 navigation guard는 각각 navigation pipeline의 어느 책임을 맡는가?
05.Vuejs-Router.pdf — RouterLink/RouterView, dynamic/named route, nested route, route props, lazy loading, navigation guard flow(p.52) ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
vue-router-navigation-guards.md — global/per-route/in-component guard,
beforeEach에서false/route location return의 의미,nextcallback style; "Global before guards are called in creation order"(L27), "you must callnextexactly once in any given pass through a navigation guard"(L68), "The Full Navigation Resolution Flow"(L287-299) ↩︎ ↩︎ ↩︎ ↩︎