내비게이션 가드
이름에서 알 수 있듯이 Vue Router가 제공하는 내비게이션 가드는 주로 리다이렉트하거나 취소함으로써 내비게이션을 보호하는 데 사용됩니다. 라우트 내비게이션 과정에 개입하는 방법은 여러 가지가 있습니다. 전역, 라우트별, 컴포넌트 내부 방식이 있습니다.
전역 Before 가드
router.beforeEach를 사용해 전역 before 가드를 등록할 수 있습니다:
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 내비게이션을 취소하려면 명시적으로 false를 반환합니다
return false
})전역 before 가드는 내비게이션이 트리거될 때마다 생성 순서대로 호출됩니다. 가드는 비동기적으로 resolve될 수 있으며, 모든 훅이 resolve되기 전까지 내비게이션은 pending 상태로 간주됩니다.
모든 가드 함수는 두 개의 인자를 받습니다:
그리고 선택적으로 다음 값 중 하나를 반환할 수 있습니다:
false: 현재 내비게이션을 취소합니다. 브라우저 URL이 바뀌었다면(사용자가 직접 바꾸었거나 뒤로 가기 버튼을 사용한 경우 포함)from라우트의 URL로 되돌아갑니다.Route Location:
router.push()를 호출하듯 라우트 location을 전달하여 다른 location으로 리다이렉트합니다. 이를 통해replace: true나name: 'home'같은 옵션도 전달할 수 있습니다. 현재 내비게이션은 폐기되고 같은from을 가진 새 내비게이션이 생성됩니다.jsrouter.beforeEach(async (to, from) => { if ( // 사용자가 인증되었는지 확인합니다 !isAuthenticated && // ❗️ 무한 리다이렉트를 피합니다 to.name !== 'Login' ) { // 사용자를 로그인 페이지로 리다이렉트합니다 return { name: 'Login' } } })
예상하지 못한 상황이 발생했다면 Error를 throw할 수도 있습니다. 이 경우에도 내비게이션이 취소되고 router.onError()를 통해 등록된 모든 콜백이 호출됩니다.
아무것도 반환하지 않거나 undefined 또는 true를 반환하면 내비게이션이 유효한 것으로 간주 되고 다음 내비게이션 가드가 호출됩니다.
위의 모든 내용은 async 함수 와 Promise에서도 동일하게 동작합니다:
router.beforeEach(async (to, from) => {
// canUserAccess()는 `true` 또는 `false`를 반환합니다
const canAccess = await canUserAccess(to)
if (!canAccess) return '/login'
})선택적 세 번째 인자 next
이전 버전의 Vue Router에서는 세 번째 인자 next도 사용할 수 있었습니다. 이는 흔한 실수 원인이었고 이를 제거하기 위한 RFC도 진행되었습니다. 하지만 여전히 지원되므로 어떤 내비게이션 가드에도 세 번째 인자를 전달할 수 있습니다. 이 경우 내비게이션 가드가 한 번 실행될 때마다 반드시 next를 정확히 한 번 호출해야 합니다. 논리 경로가 겹치지 않는다면 여러 번 등장해도 되지만, 그렇지 않으면 훅이 resolve되지 않거나 오류가 발생합니다. 아래는 인증되지 않았을 때 사용자를 /login으로 리다이렉트하는 잘못된 예시 입니다:
// 잘못된 예
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
// 사용자가 인증되지 않은 경우 `next`가 두 번 호출됩니다
next()
})올바른 버전은 다음과 같습니다:
// 올바른 예
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})전역 Resolve 가드
router.beforeResolve로 전역 가드를 등록할 수 있습니다. 모든 내비게이션에서 트리거된다는 점에서 router.beforeEach와 비슷하지만, resolve 가드는 모든 컴포넌트 내부 가드와 비동기 라우트 컴포넌트가 resolve된 뒤, 내비게이션이 확정되기 직전에 호출됩니다. 다음은 사용자 정의 meta 속성 requiresCamera를 정의한 라우트에 대해 사용자가 카메라 접근 권한을 부여했는지 확인하는 예시입니다:
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ...오류를 처리한 다음 내비게이션을 취소합니다
return false
} else {
// 예상하지 못한 오류이므로 내비게이션을 취소하고 오류를 전역 핸들러에 전달합니다
throw error
}
}
}
})router.beforeResolve는 사용자가 페이지에 들어갈 수 없는 경우 수행하고 싶지 않은 데이터 가져오기나 다른 작업을 처리하기에 이상적인 위치입니다.
전역 After 훅
전역 after 훅도 등록할 수 있습니다. 하지만 가드와 달리 이 훅에는 next 함수가 전달되지 않으며 내비게이션에 영향을 줄 수 없습니다:
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})이 훅은 analytics, 페이지 제목 변경, 페이지 알림 같은 접근성 기능 등 다양한 작업에 유용합니다.
내비게이션 실패도 세 번째 인자로 전달됩니다:
router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})전용 가이드에서 내비게이션 실패에 대해 더 알아볼 수 있습니다.
가드 내부의 전역 주입
Vue 3.3부터는 내비게이션 가드 안에서 inject()를 사용할 수 있습니다. 이는 pinia store 같은 전역 속성을 주입할 때 유용합니다. app.provide()로 제공한 것은 router.beforeEach(), router.beforeResolve(), router.afterEach() 안에서도 접근할 수 있습니다:
const app = createApp(App)
app.provide('global', 'hello injections')
// router.ts 또는 main.ts
router.beforeEach((to, from) => {
const global = inject('global') // 'hello injections'
// Pinia 스토어
const userStore = useAuthStore()
// ...
})라우트별 가드
라우트 구성 객체에 beforeEnter 가드를 직접 정의할 수 있습니다:
const 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 },
],
},
]위 예제의 beforeEnter는 /user/list와 /user/details 사이를 이동할 때는 호출되지 않습니다. 둘이 같은 부모를 공유하기 때문입니다. 대신 beforeEnter 가드를 details 라우트에 직접 두면 이 두 라우트 사이를 이동할 때 호출됩니다.
TIP
라우트별 가드와 비슷한 동작은 라우트 meta 필드와 전역 내비게이션 가드를 사용해서도 구현할 수 있습니다.
컴포넌트 내부 가드
마지막으로 라우트 컴포넌트(라우터 구성에 전달되는 컴포넌트) 내부에 라우트 내비게이션 가드를 직접 정의할 수 있습니다
Options API 사용하기
라우트 컴포넌트에 다음 옵션을 추가할 수 있습니다:
beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave
<script>
export default {
beforeRouteEnter(to, from) {
// 이 컴포넌트를 렌더링하는 라우트가 확정되기 전에 호출됩니다.
// 이 가드가 호출될 때는 컴포넌트가 아직 생성되지 않았기 때문에
// `this` 컴포넌트 인스턴스에 접근할 수 없습니다!
},
beforeRouteUpdate(to, from) {
// 이 컴포넌트를 렌더링하는 라우트가 바뀌었지만, 새 라우트에서 이 컴포넌트가 재사용될 때 호출됩니다.
// 예를 들어 `/users/:id` 같은 params 라우트가 있을 때 `/users/1`과 `/users/2` 사이를 이동하면
// 같은 `UserDetails` 컴포넌트 인스턴스가 재사용되고, 이 훅이 호출됩니다.
// 이 시점에는 컴포넌트가 마운트되어 있으므로 내비게이션 가드는 `this` 컴포넌트 인스턴스에 접근할 수 있습니다.
},
beforeRouteLeave(to, from) {
// 이 컴포넌트를 렌더링하는 라우트에서 벗어나려 할 때 호출됩니다.
// `beforeRouteUpdate`와 마찬가지로 `this` 컴포넌트 인스턴스에 접근할 수 있습니다.
},
}
</script>beforeRouteEnter 가드는 this에 접근할 수 없습니다. 내비게이션이 확정되기 전에 호출되므로 새로 진입하는 컴포넌트가 아직 생성되지 않았기 때문입니다.
하지만 next에 콜백을 전달하면 인스턴스에 접근할 수 있습니다. 이 콜백은 내비게이션이 확정되었을 때 호출되며, 컴포넌트 인스턴스가 인자로 전달됩니다:
beforeRouteEnter (to, from, next) {
next(vm => {
// `vm`을 통해 컴포넌트 public 인스턴스에 접근합니다
})
}beforeRouteEnter만이 next에 콜백을 전달하는 것을 지원한다는 점에 유의하세요. beforeRouteUpdate와 beforeRouteLeave에서는 이미 this를 사용할 수 있으므로 콜백 전달은 불필요하며 지원되지 않습니다:
beforeRouteUpdate (to, from) {
// 그냥 `this`를 사용하세요
this.name = to.params.name
}leave 가드 는 보통 사용자가 저장하지 않은 편집 내용을 남긴 채 라우트를 실수로 떠나는 것을 막기 위해 사용됩니다. false를 반환하면 내비게이션을 취소할 수 있습니다.
beforeRouteLeave (to, from) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (!answer) return false
}Composition API 사용하기
Composition API로 컴포넌트를 작성하고 있다면 각각 onBeforeRouteUpdate와 onBeforeRouteLeave를 통해 update 가드와 leave 가드를 추가할 수 있습니다. 자세한 내용은 Composition API 섹션을 참고하세요.
전체 내비게이션 해석 흐름
- 내비게이션이 트리거됩니다.
- 비활성화되는 컴포넌트에서
beforeRouteLeave가드를 호출합니다. - 전역
beforeEach가드를 호출합니다. - 재사용되는 컴포넌트에서
beforeRouteUpdate가드를 호출합니다. - 라우트 설정의
beforeEnter를 호출합니다. - 비동기 라우트 컴포넌트를 resolve합니다.
- 활성화되는 컴포넌트에서
beforeRouteEnter를 호출합니다. - 전역
beforeResolve가드를 호출합니다. - 내비게이션이 확정됩니다.
- 전역
afterEach훅을 호출합니다. - DOM 업데이트가 트리거됩니다.
beforeRouteEnter가드에서next에 전달한 콜백을 인스턴스화된 인스턴스와 함께 호출합니다.