Vue Router 핵심 정리

# 설치 및 구성
다음과 같이 vue-router
를 설치합니다.
1npm i vue-router
다음의 폴더 및 파일 구조로 시작합니다.
12345678├─src/ │ ├─routes/ │ │ ├─pages/ │ │ │ ├─AboutPage.vue │ │ │ └─HomePage.vue │ │ └─index.ts | ├─App.vue │ └─main.ts
/src/routes/pages
폴더에 사용할 페이지 컴포넌트를 추가합니다.
우선 간단하게 Home과 About 페이지를 추가하겠습니다.
123<template> <h1>Home page!</h1> </template>
123<template> <h1>About page!</h1> </template>
보여줄 페이지를 추가했다면 이제 /src/routes/index.ts
파일에서 프로젝트의 라우터를 구성합니다.
createRouter
함수를 사용해 라우터를 생성합니다.
이때 history
옵션에는 라우팅 모드를 지정하고, routes
옵션에는 라우트 객체(정보)를 배열로 전달합니다.
각 라우트 객체는 path
속성에 접근 경로를, component
속성에 경로가 일치할 때 표시할 컴포넌트를 지정합니다.
즉, 다음 예제에서 사용자는 /
, /about
주소로 접근할 수 있습니다.
그리고 createRouter
함수로 생성한 라우터 객체(router
)를 외부에서 사용할 수 있도록 내보냅니다.
12345678910111213141516171819import { createRouter, createWebHistory } from 'vue-router' import HomePage from './pages/HomePage.vue' import AboutPage from './pages/AboutPage.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: HomePage }, { path: '/about', component: AboutPage } ] }) export default router
이제 /src/main.ts
로 이동해 생성된 라우터 객체를 가져와 프로젝트의 플러그인으로 등록합니다.
1234567import { createApp } from 'vue' import App from './App.vue' import router from './routes' createApp(App) .use(router) .mount('#app')
마지막으로 프로젝트의 최상위 컴포넌트(/src/App.vue
)에서 사용자의 접근 경로에 맞게 각 페이지가 출력될 위치를 <RouterView />
컴포넌트로 지정합니다.
1234567<script setup lang="ts"> import { RouterView } from 'vue-router' </script> <template> <RouterView /> </template>
# 레이아웃
1234567891011121314├─src/ │ ├─components/ │ │ └─TheHeader.vue │ ├─routes/ │ │ ├─layouts/ │ │ │ ├─DefaultLayout.vue │ │ │ ├─EmptyLayout.vue │ │ │ └─LayoutProvider.vue │ │ ├─pages/ │ │ │ ├─AboutPage.vue │ │ │ └─HomePage.vue │ │ └─index.ts | ├─App.vue │ └─main.ts
헤더를 만들어 각 페이지로 이동할 수 있는 내비게이션 버튼을 제공하고 필요한 모든 페이지에서 표시할 수 있도록 해봅시다.
먼저 다음과 같이 <TheHeader>
컴포넌트를 만듭니다.
12345678<template> <header> <nav> <a href="/">Home</a> <a href="/about">About</a> </nav> </header> </template>
헤더나 푸터 같이 대부분의 페이지에서 공통적으로 사용되는 구조를 각 페이지 컴포넌트에서 직접 추가하면, 페이지 전환 시마다 불필요한 리렌더링이 발생하게 됩니다.
그래서 공통 구조를 다시 렌더링하지 않도록 별도의 레이아웃 구조를 제공해야 합니다.
다음과 같이 기본 레이아웃(DefaultLayout.vue
)을 추가해서 앞서 만든 헤더를 표시합니다.
그리고 접근 경로에 맞게 페이지가 출력될 위치를 <slot>
컴포넌트로 지정합니다.
12345678<script setup lang="ts"> import TheHeader from '@/components/TheHeader.vue' </script> <template> <TheHeader /> <slot /> </template>
헤더나 푸터 등의 공통 레이아웃이 없는 페이지도 있을 수 있으니, 다음과 같이 빈 레이아웃(EmptyLayout.vue
)도 추가하겠습니다.
123<template> <slot /> </template>
페이지에서 어떤 레이아웃을 사용할 것인지는 다음과 같이 라우트 객체의 meta.layout
속성에서 지정합니다.
다만 이 과정에서는 기본 레이아웃만 사용할 것이므로 따로 명시하지 않습니다.
12345678910111213141516171819202122232425import { createRouter, createWebHistory } from 'vue-router' import HomePage from './pages/HomePage.vue' import AboutPage from './pages/AboutPage.vue' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: HomePage, meta: { layout: 'Default' } }, { path: '/about', component: AboutPage, meta: { layout: 'Empty' } } ] }) export default router
이제 각 페이지가 레이아웃 안에서 출력되도록 레이아웃 제공자(LayoutProvider.vue
)를 추가하겠습니다.
프로젝트에서 사용할 레이아웃을 목록(layouts
)으로 정의하고, meta.layout
속성 값에 맞게 레이아웃을 출력하도록 동적 컴포넌트(<Component>
)를 사용합니다.
현재 라우트 정보는 컴포넌트에서 useRoute()
훅을 호출해 얻을 수 있고, meta.layout
속성이 없으면 기본 레이아웃(DefaultLayout.vue
)을 사용하도록 작성합니다.
혹시 meta.layout
속성에서 정의된 레이아웃 이름(Default
나 Empty
)이 아닌 잘못된 이름이 사용되지 않도록 라우터 인터페이스(RouteMeta
)를 확장하면 안전한 타입으로 관리할 수 있습니다.
12345678910111213141516171819202122232425262728<script setup lang="ts"> import { RouterView, useRoute } from 'vue-router' import Default from './DefaultLayout.vue' import Empty from './EmptyLayout.vue' // 라우터 인터페이스 확장 declare module 'vue-router' { interface RouteMeta { layout?: keyof typeof layouts } } // 사용할 레이아웃 목록 지정 const layouts = { Default, Empty } as const // 현재 라우트 객체(정보) 가져오기 const route = useRoute() </script> <template> <!-- 동적 레이아웃 출력 및 기본 레이아웃 지정 --> <Component :is="layouts[route.meta.layout || 'Default']"> <RouterView /> </Component> </template>
마지막으로, 생성한 레이아웃 제공자 컴포넌트(<LayoutProvider>
)로 페이지 출력 위치를 감싸면 이제 모든 페이지가 레이아웃을 거쳐 출력됩니다.
123456789<script setup lang="ts"> import LayoutProvider from '@/routes/layouts/LayoutProvider.vue' </script> <template> <LayoutProvider> <RouterView /> </LayoutProvider> </template>
# 스크롤 복원
사용자가 뒤로 혹은 앞으로 가기를 할 때 페이지의 스크롤 위치를 복원하거나, 새로운 페이지의 스크롤 위치를 최상단으로 이동시켜 사용자에게 더 나은 페이지 탐색 경험을 제공할 수 있습니다.createRouter
함수의 scrollBehavior
옵션에서 스크롤 위치를 처리할 수 있습니다.
1234567891011121314// ... const router = createRouter({ history: createWebHistory(), scrollBehavior(to, from, savedPosition) { if (savedPosition) return savedPosition return { top: 0, left: 0 } }, routes: [ // ... ] }) export default router
# 탐색
# 컴포넌트 방식
앞서 작성한 헤더의 <a>
요소 탐색은 항상 페이지 전체를 로드합니다.
대신에 <RouterLink>
컴포넌트를 사용하면 탐색 시 필요한 부분만 업데이트하여 더 나은 사용자 경험을 제공할 수 있습니다.
<RouterLink>
컴포넌트는 href
대신 to
속성에 이동할 경로를 지정합니다.
그리고 to
속성에 지정된 경로와 현재 경로의 일치 여부에 따라서 활성 클래스(router-link-active
, router-link-exact-active
)가 자동으로 추가됩니다.
활성 클래스에 맞게 내비게이션의 스타일을 추가하면, 사용자가 현재 페이지를 더 명확하게 파악할 수 있습니다.
다음과 같이 <TheHeader>
컴포넌트를 수정합니다.
1234567891011121314151617181920212223<script setup lang="ts"> import { RouterLink } from 'vue-router' </script> <template> <header> <nav> <RouterLink to="/">Home</RouterLink> <RouterLink to="/about">About</RouterLink> </nav> </header> </template> <style scoped> nav { display: flex; gap: 10px; } .router-link-exact-active { font-weight: bold; color: red; } </style>
router-link-active
클래스는 지정된 경로가 현재 경로의 일부인 경우에 추가되며, router-link-exact-active
클래스는 지정된 경로와 현재 경로가 완전히 일치하는 경우에만 추가됩니다.
zoom_out_map
지정 경로(to ) |
현재 경로(URL) | router-link-active |
router-link-exact-active |
---|---|---|---|
/ |
/ |
✅ | ✅ |
/ |
/about |
❌ | ❌ |
/about |
/about |
✅ | ✅ |
/movies |
/movies |
✅ | ✅ |
/movies |
/movies/abc123 |
✅ | ❌ |
/movies/abc123 |
/movies/abc123 |
✅ | ✅ |
to
속성에는 기본적으로 경로 전체를 문자로 작성합니다.
또는 필수적인 기본 경로(path
)와 함께 쿼리스트링(query
), 해시 프래그먼트(hash
)를 선택적으로 포함하는 객체로 전달할 수 있습니다.
또는 필수적인 라우트 이름(name
)과 함께 동적 파라미터(params
), 쿼리스트링, 해시를 선택적으로 포함하는 객체로 전달할 수도 있습니다.
123456789101112131415161718192021222324<template> <RouterLink :to="`/movies/${movieId}?plot=full#title`"> 페이지 이동 </RouterLink> <RouterLink :to="{ path: `/movies/${movieId}`, query: { plot: 'full' }, hash: '#title' }"> 페이지 이동 </RouterLink> <RouterLink :to="{ name: 'MovieDetails', params: { movieId }, query: { plot: 'full' }, hash: '#title' }"> 페이지 이동 </RouterLink> </template>
라우트 이름은 라우트 객체의 name
속성에 선택적으로 작성하며, 고유해야 합니다.
라우트를 이름으로 관리하면 경로가 변경되더라도 코드 수정이 최소화되고, 잘못된 경로를 실수로 작성하는 것을 방지할 수 있습니다.
1234567891011121314151617181920212223242526272829303132333435363738394041// ... const router = createRouter({ // ... routes: [ { name: 'Home', path: '/', component: HomePage }, { name: 'About', path: '/about', component: AboutPage }, { name: 'SignIn', path: '/signin', component: SignInPage }, { name: 'Movies', path: '/movies', component: MoviesPage children: [ { name: 'MovieDetails', path: ':movieId', component: MovieDetailsPage } ] }, { name: 'NotFound', path: '/:pathMatch(.*)*', component: NotFoundPage } ] }) export default router
# 프로그래밍 방식
<RouterLink>
컴포넌트를 사용하는 대신, 프로그래밍 방식으로도 탐색을 구현할 수 있습니다.useRouter()
훅을 호출하면, 페이지 이동을 처리하는 여러 메서드를 가진 라우터 인스턴스를 얻을 수 있습니다.
push
: 새로운 페이지로 이동replace
: 현재 페이지를 대체하고 새로운 페이지로 이동back
: 뒤로가기forward
: 앞으로가기go
: 지정된 숫자만큼 뒤로 혹은 앞으로 이동
1234567891011121314<script setup lang="ts"> import { useRouter } from 'vue-router' const router = useRouter() </script> <template> <button @click="router.push('/')">페이지 이동</button> <button @click="router.replace('/about')">페이지 이동(뒤로가기 불가)</button> <button @click="router.back()">뒤로가기</button> <button @click="router.forward()">앞으로가기</button> <button @click="router.go(-1)">뒤로가기</button> <button @click="router.go(1)">앞으로가기</button> </template>
앞서 살펴본 <RouterLink>
컴포넌트의 to
속성과 마찬가지로, push
와 replace
메서드에서도 경로를 문자 혹은 객체로 전달할 수 있습니다.
1234567891011121314151617181920function onlyString(movieId: string) { router.push(`/movies/${movieId}?plot=full#title`) } function pathObject(movieId: string) { router.push({ path: `/movies/${movieId}`, query: { plot: 'full' }, hash: '#title' }) } function nameObject(movieId: string) { router.push({ name: 'MovieDetails', params: { movieId }, query: { plot: 'full' }, hash: '#title' }) }
# 동적 경로 일치
동적 경로(Dynamic Routes)는 URL의 일부를 변수처럼 활용할 수 있는 편리한 기능입니다.
이를 통해 단일 라우트 컴포넌트로 다양한 페이지를 동적으로 관리할 수 있어 재사용성과 유연성이 크게 증가합니다.
예를 들어, 영화의 상세 정보를 표시하는 페이지를 모두 개별적으로 만들지 않고 하나의 컴포넌트로 모든 영화의 상세 정보를 효과적으로 표시할 수 있습니다.
아래와 같이 동적 경로를 적용해 봅시다.
123456789101112131415├─src/ │ ├─components/ │ │ └─TheHeader.vue │ ├─routes/ │ │ ├─layouts/ │ │ │ ├─DefaultLayout.vue │ │ │ ├─EmptyLayout.vue │ │ │ └─LayoutProvider.vue │ │ ├─pages/ │ │ │ ├─AboutPage.vue │ │ │ ├─HomePage.vue │ │ │ └─MovieDetailsPage.vue │ │ └─index.ts | ├─App.vue │ └─main.ts
먼저 영화 상세 정보를 표시할 페이지(MovieDetailsPage.vue
)를 작성합니다.useRoute()
훅을 호출해 얻은 라우트 정보의 params
속성에서 movieId
라는 이름의 동적 경로 정보를 얻습니다.
그리고 이 movieId
를 사용해 API 요청으로 영화 정보를 가져오고 출력합니다.
1234567891011121314151617181920212223242526272829303132<script setup lang="ts"> import { ref, onMounted } from 'vue' import { useRoute } from 'vue-router' interface Movie { imdbID: string Title: string Poster: string } const route = useRoute() const { movieId } = route.params const movie = ref<Movie | null>(null) async function fetchMovieDetails() { const res = await fetch(`https://omdbapi.com/?apikey=7035c60c&i=${movieId}`) movie.value = await res.json() } onMounted(() => { fetchMovieDetails() }) </script> <template> <template v-if="movie"> <h1>{{ movie.Title }}</h1> <img :src="movie.Poster" :alt="movie.Title" /> </template> </template>
영화 상세 정보 페이지를 작성했으니 접근 경로를 지정합니다.
라우트 객체의 path
속성에 '/movies/:movieId'
와 같이 :
기호를 이용해서 동적으로 경로를 일치시키는 변수(movieId
)를 지정합니다.
12345678910111213141516// ... import MovieDetailsPage from './pages/MovieDetailsPage.vue' const router = createRouter({ // ... routes: [ // ... { name: 'MovieDetails', path: '/movies/:movieId', component: MovieDetailsPage } ] }) export default router
준비된 페이지로 이동할 수 있도록 헤더에 내비게이션 버튼을 추가합니다.
다음 예시에 작성된 이동 경로의 tt4154796
는 'Avengers: Endgame(2019)' 영화의 ID입니다.
12345678910111213<script setup lang="ts"> import { RouterLink } from 'vue-router' </script> <template> <header> <nav> <RouterLink to="/">Home</RouterLink> <RouterLink to="/about">About</RouterLink> <RouterLink to="/movies/tt4154796">Movie(Avengers: Endgame)</RouterLink> </nav> </header> </template>
# 중첩 경로
하나의 라우트 컴포넌트 안에서 다른 라우트 컴포넌트를 포함할 수 있습니다.
아래 예제에서는 영화를 검색할 수 있는 페이지를 만들어 검색 결과를 선택해 영화 상세 정보 출력하도록 합니다.
다만 영화 상세 정보를 별도의 페이지가 아닌 모달 형태로 표시합니다.
12345678910111213141516├─src/ │ ├─components/ │ │ └─TheHeader.vue │ ├─routes/ │ │ ├─layouts/ │ │ │ ├─DefaultLayout.vue │ │ │ ├─EmptyLayout.vue │ │ │ └─LayoutProvider.vue │ │ ├─pages/ │ │ │ ├─AboutPage.vue │ │ │ ├─HomePage.vue │ │ │ ├─MovieDetailsPage.vue │ │ │ └─MoviesPage.vue │ │ └─index.ts | ├─App.vue │ └─main.ts
먼저 영화 검색 페이지(MoviesPage.vue
)를 작성합니다.
검색된 영화 목록에서 원하는 영화를 선택하면 영화 상세 정보 페이지로 이동합니다.
그리고 <RouterView />
컴포넌트를 사용해, 영화 상세 정보 페이지가 검색 페이지의 일부분으로 표시되도록 중첩 경로를 적용합니다.
123456789101112131415161718192021222324252627282930313233343536373839404142<script setup lang="ts"> import { ref } from 'vue' import { RouterLink } from 'vue-router' interface Movie { imdbID: string Title: string } const searchTitle = ref('') const movies = ref<Movie[]>([]) async function fetchMovies() { const res = await fetch( `https://omdbapi.com/?apikey=7035c60c&s=${searchTitle.value}` ) const { Search } = await res.json() movies.value = Search } </script> <template> <div> <h1>Movies page!</h1> <div> <input v-model="searchTitle" @keydown.enter="fetchMovies" /> <button @click="fetchMovies">검색</button> </div> <ul> <li v-for="movie in movies" :key="movie.Title"> <RouterLink :to="`/movies/${movie.imdbID}`"> {{ movie.Title }} </RouterLink> </li> </ul> <RouterView /> </div> </template>
작성한 영화 검색 페이지를 라우트 객체로 등록합니다.
이때 앞서 만든 영화 상세 정보 페이지의 라우트 객체를 검색 페이지의 children
속성으로 이동시켜 중첩 경로를 적용하고, path
속성의 경로를 /movies/:movieId
에서 :movieId
로 수정합니다.
부모 라우트(Movies)의 경로가 이미 /movies
이므로, 자식 라우트(MovieDetails)에서는 /movies
를 생략하고 :movieId
만 작성하면 됩니다.
이제 위에서 설명한 대로, 영화 상세 정보 페이지는 영화 검색 페이지의 <RouterView />
컴포넌트 위치에 출력됩니다.
123456789101112131415161718192021222324// ... import MoviesPage from './pages/MoviesPage.vue' import MovieDetailsPage from './pages/MovieDetailsPage.vue' const router = createRouter({ history: createWebHistory(), routes: [ // ... { name: 'Movies', path: '/movies', component: MoviesPage, children: [ { name: 'MovieDetails', path: ':movieId', component: MovieDetailsPage } ] } ] }) export default router
이제 영화 상세 정보 페이지를 모달 형태로 표시해 봅시다.
모달로 표시될 요소 구조와 함께 스타일을 추가합니다.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849<script setup lang="ts"> // ... </script> <template> <div class="modal"> <div class="overlay" @click="closeModal" /> <div class="content"> <template v-if="movie"> <h1>{{ movie.Title }}</h1> <img :src="movie.Poster" :alt="movie.Title" /> </template> </div> </div> </template> <style scoped> .modal { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; } .overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); cursor: pointer; } .content { position: relative; max-width: 500px; padding: 20px 30px; border-radius: 10px; box-shadow: 0 10px 10px rgba(0, 0, 0, 0.1); background-color: white; } </style>
준비가 끝났으니, 영화 검색 페이지로 이동할 수 있는 내비게이션 버튼을 추가하고 테스트해 봅시다.
1234567891011121314<!-- ... --> <template> <header> <nav> <RouterLink to="/">Home</RouterLink> <RouterLink to="/about">About</RouterLink> <RouterLink to="/movies">Movies</RouterLink> <RouterLink to="/movies/tt4154796">Movie(Avengers: Endgame)</RouterLink> </nav> </header> </template> <!-- ... -->
# 찾을 수 없는 페이지
1234567891011121314151617├─src/ │ ├─components/ │ │ └─TheHeader.vue │ ├─routes/ │ │ ├─layouts/ │ │ │ ├─DefaultLayout.vue │ │ │ ├─EmptyLayout.vue │ │ │ └─LayoutProvider.vue │ │ ├─pages/ │ │ │ ├─AboutPage.vue │ │ │ ├─HomePage.vue │ │ │ ├─MovieDetailsPage.vue │ │ │ ├─MoviesPage.vue │ │ │ └─NotFoundPage.vue │ │ └─index.ts | ├─App.vue │ └─main.ts
사용자가 정의하지 않은 경로로 접근했을 때 표시할 페이지를 만들어 봅시다.
다음과 같이 NotFoundPage.vue
를 작성합니다.
123<template> <h1>404 Not Found page!</h1> </template>
그리고 모든 경로에 일치하는 라우트 객체를 추가합니다.pathMatch
라는 동적 경로 이름으로 모든 하위 경로를 일치시키기 위해 정규식도 같이 작성합니다.
주의할 점은, 라우트 객체는 모든 라우트 객체의 일치 확인이 끝나는 가장 마지막 위치에 추가해야 합니다.
12345678910111213141516// ... import NotFoundPage from './pages/NotFoundPage.vue' const router = createRouter({ // ... routes: [ // ... { name: 'NotFound', path: '/:pathMatch(.*)*', component: NotFoundPage } ] }) export default router
# 내비게이션 가드
내비게이션 가드는 페이지 이동 전에 특정 조건 여부에 맞게 이동을 취소하거나 다른 경로로 리다이렉트할 수 있는 기능을 말합니다.
주로 로그인 등의 사용자 인증 여부를 확인하는 용도로 사용합니다.
12345678910111213141516171819202122├─src/ │ ├─components/ │ │ └─TheHeader.vue │ ├─routes/ │ │ ├─guards/ │ │ │ ├─guestOnly.ts │ │ │ ├─requiresAuth.ts │ │ │ └─index.ts │ │ ├─layouts/ │ │ │ ├─DefaultLayout.vue │ │ │ ├─EmptyLayout.vue │ │ │ └─LayoutProvider.vue │ │ ├─pages/ │ │ │ ├─AboutPage.vue │ │ │ ├─HomePage.vue │ │ │ ├─MovieDetailsPage.vue │ │ │ ├─MoviesPage.vue │ │ │ ├─NotFoundPage.vue │ │ │ └─SignInPage.vue │ │ └─index.ts | ├─App.vue │ └─main.ts
먼저 로그인 페이지(SignInPage.vue
)를 작성합니다.
테스트를 위해, 사용자가 아이디(이메일)와 비밀번호를 모두 입력하면 로그인 성공으로 처리하고 접근 토큰(accessToken
)을 로컬 스토리지에 저장합니다.
1234567891011121314151617181920212223242526272829303132333435<script setup lang="ts"> import { useRoute, useRouter } from 'vue-router' const route = useRoute() const router = useRouter() function handleSubmit(event: Event) { // `<form>`의 데이터를 가져와 사용하기 쉽게 객체로 변환합니다. const formData = new FormData(event.target as HTMLFormElement) const { email, password } = Object.fromEntries(formData) as Record<string, string> // 로그인 정보가 모두 있으면, 임시로 로그인 처리합니다. if (email && password) { localStorage.setItem('accessToken', 'abcd1234') const redirectTo = (route.query.redirectTo as string) || '/' router.push(redirectTo) } } </script> <template> <div> <h1>Sign In page!</h1> <form @submit.prevent="handleSubmit"> <input name="email" type="email" placeholder="Email" /> <input name="password" type="password" placeholder="Password" /> <button type="submit">로그인</button> </form> </div> </template>
로그인 페이지를 라우트 객체로 등록합니다.
이때 라우트 객체의 meta.guestOnly
속성을 지정해서 로그인 페이지에는 로그인하지 않은 사용자만 접근할 수 있도록 합니다.
반대로 영화 검색 및 상세 정보 페이지는 로그인한 사용자만 접근할 수 있도록 meta.auth
속성을 지정합니다.
1234567891011121314151617181920212223242526272829// ... const router = createRouter({ // ... routes: [ { name: 'SignIn', path: '/signin', component: SignInPage, meta: { guestOnly: true } }, { name: 'Movies', path: '/movies', component: MoviesPage, meta: { auth: true }, children: [ // ... ] } // ... ] }) export default router
앞서 지정한 meta
속성의 값에 따라 동작할 내비게이션 가드를 작성합니다.
requiresAuth
는 로그인한 사용자만 접근할 수 있도록 하는 가드이고, guestOnly
는 로그인하지 않은 사용자만 접근할 수 있도록 하는 가드입니다.
123456789101112131415import type { RouteGuard } from '.' export const requiresAuth: RouteGuard = { guard(to) { if (to.meta.auth) { const token = localStorage.getItem('accessToken') // 토큰이 유효한지 확인! if (!token) { return false } } return true }, redirect: '/signin' }
123456789101112131415import type { RouteGuard } from '.' export const guestOnly: RouteGuard = { guard(to) { if (to.meta.noAuth) { const token = localStorage.getItem('accessToken') // 유효 토큰이 있는지 확인! if (token) { return false } } return true }, redirect: '/' }
이제 작성한 내비게이션 가드를 적용해 봅시다.router.beforeEach
메서드의 콜백은 모든 페이지의 접근 직전에 호출되며, 매개변수 to
는 이동할 페이지의 라우트 객체입니다.
이를 통해 각 가드에서 라우트 객체의 meta
속성의 값을 확인할 수 있습니다.
123456789101112131415import type { RouteLocationNormalizedGeneric } from 'vue-router' import router from '@/routes' import { requiresAuth } from './requiresAuth' import { guestOnly } from './requiresNoAuth' router.beforeEach(to => { if (!requiresAuth.guard(to)) return requiresAuth.redirect if (!guestOnly.guard(to)) return guestOnly.redirect return true }) export interface RouteGuard { guard(to: RouteLocationNormalizedGeneric): boolean redirect?: string }
# 페이지 전환 애니메이션
Vue <Transition>
컴포넌트를 사용하면, 페이지가 바뀔 때마다의 전환 애니메이션을 쉽게 추가할 수 있습니다.
0.3초에 걸쳐 기존 페이지가 사라지고 새 페이지가 나타나는 애니메이션을 추가합니다.
1234567891011121314151617181920212223242526272829303132333435363738394041424344<script setup lang="ts"> import { RouterView, useRoute } from 'vue-router' import Default from './DefaultLayout.vue' import Empty from './EmptyLayout.vue' declare module 'vue-router' { interface RouteMeta { layout?: keyof typeof layouts } } const layouts = { Default, Empty } as const const route = useRoute() </script> <template> <Component :is="layouts[route.meta.layout || 'Default']"> <Transition name="fade" mode="out-in"> <RouterView /> </Transition> </Component> </template> <style scoped> .fade-enter-active, .fade-leave-active { transition: opacity 3s; } .fade-enter-from, .fade-leave-to { opacity: 0; position: absolute; } .fade-enter-to, .fade-leave-from { opacity: 1; } </style>
# 배포
HTML5 Mode(createWebHistory
)를 사용하는 Vue Router 프로젝트를 배포할 때는, 모든 요청을 index.html
로 리다이렉트해야 하는 서버 설정이 필요합니다.
이를 통해 사용자가 직접 URL을 입력해 특정 페이지에 접근하거나 페이지를 새로고침해도 애플리케이션이 정상적으로 작동하도록 만들어야 합니다.
간단하게 배포할 수 있는 주요 호스팅 서비스별로 필요한 설정을 살펴봅시다.
# Vercel
Vercel 서비스로 배포하는 경우, 프로젝트의 루트 경로에 vercel.json
파일을 생성하고 다음 구성을 추가합니다.
123{ "rewrites": [{ "source": "/:path*", "destination": "/index.html" }] }
# Netlify
Netlify 서비스로 배포하는 경우, 프로젝트의 공개 폴더(/public
) 경로에 _redirects
파일을 생성하고 다음 구성을 추가합니다.
1/* /index.html 200
또는 프로젝트의 루트 경로에 netlify.toml
파일을 생성하고 다음 구성을 추가합니다.
1234[[redirects]] from = "/*" to = "/index.html" status = 200
# Firebase
Firebase 서비스로 배포하는 경우, 프로젝트의 루트 경로에 firebase.json
파일을 생성하고 다음 구성을 추가합니다.
1234567891011{ "hosting": { "public": "dist", "rewrites": [ { "source": "**", "destination": "/index.html" } ] } }
끝까지 읽어주셔서 감사합니다.
좋아요와 응원 댓글은 블로그 운영에 큰 힘이 됩니다!