Routing et Navigation Guards
Dans ce chapitre, on ajoute la navigation multi-pages au Show Watchlist : une page d'accueil, une page de connexion et une page de détail par film.
Pourquoi un router ?
Dans une SPA, une seule page HTML est chargée. La navigation entre les "pages" est gérée côté client : Vue Router intercepte les changements d'URL et affiche le composant correspondant sans rechargement.
Sans Vue Router, la navigation entre vues s'effectuerait manuellement avec v-if — ingérable dès qu'on a plusieurs pages.
Installation
npm install vue-router
Configurer le router
// src/router.ts
import { createRouter, createWebHistory } from 'vue-router'
import HomePage from './pages/HomePage.vue'
import ShowDetailPage from './pages/ShowDetailPage.vue'
export const ROUTES = {
HOME: '/',
SHOW_DETAIL: '/show/:id',
} as const
const routes = [
{ path: ROUTES.HOME, component: HomePage },
{ path: ROUTES.SHOW_DETAIL, component: ShowDetailPage },
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
// main.ts
import naive from 'naive-ui'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.use(naive)
app.mount('#app')
createWebHistory()utilise l'API History du navigateur (URLs propres sans#). Le serveur doit être configuré pour renvoyerindex.htmlpour toutes les routes.
Afficher les pages avec RouterView
<RouterView /> est le composant qui affiche la page correspondant à l'URL courante. Il se place dans App.vue.
<!-- App.vue -->
<template>
<NavMenu />
<main>
<RouterView />
</main>
</template>
<script setup lang="ts">
import NavMenu from '@/components/NavMenu.component.vue'
</script>
RouterLink : navigation déclarative
<RouterLink> génère un <a> qui navigue sans recharger la page.
<!-- src/components/NavMenu.component.vue -->
<template>
<NSpace align="center" :size="8">
<RouterLink :to="ROUTES.HOME">
<NButton quaternary :type="route.path === ROUTES.HOME ? 'primary' : 'default'">
Accueil
</NButton>
</RouterLink>
</NSpace>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { ROUTES } from '@/router'
const route = useRoute()
</script>
Routes dynamiques avec paramètres
Une route comme /show/:id accepte n'importe quel identifiant. Le paramètre est accessible via useRoute().
<!-- src/pages/ShowDetailPage.vue -->
<template>
<div>
<p v-if="!show">Film introuvable.</p>
<NCard v-else :title="show.title">
<p>{{ show.genre }} — {{ show.year }}</p>
<NTag :type="show.seen ? 'success' : 'default'">
{{ show.seen ? '✓ Vu' : 'À voir' }}
</NTag>
</NCard>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import type { Show } from '@/types'
const route = useRoute()
const shows: Show[] = [
{ id: 1, title: 'Inception', genre: 'Sci-Fi', year: 2010, seen: true },
{ id: 2, title: 'The Dark Knight', genre: 'Action', year: 2008, seen: false },
{ id: 3, title: 'Interstellar', genre: 'Sci-Fi', year: 2014, seen: false },
]
const show = computed(() =>
shows.find((m) => m.id === Number(route.params.id))
)
</script>
Et dans ShowCard, on ajoute un lien vers la page de détail :
<template>
<NCard :title="show.title">
<!-- ... -->
<template #footer>
<NSpace>
<NButton type="primary" @click="emit('toggle-seen', show)">Basculer</NButton>
<RouterLink :to="`/show/${show.id}`">
<NButton>Détail</NButton>
</RouterLink>
</NSpace>
</template>
</NCard>
</template>
Navigation programmatique
useRouter() permet de naviguer par code (après une action, une connexion, etc.).
import { useRouter } from 'vue-router'
const router = useRouter()
router.push(ROUTES.HOME) // Naviguer vers une route
router.push(`/show/${show.id}`) // Avec un paramètre dynamique
router.back() // Revenir en arrière
router.replace(ROUTES.HOME) // Remplacer l'historique (pas de retour possible)
Navigation Guards
Les navigation guards permettent de contrôler l'accès aux routes : rediriger l'utilisateur non connecté vers la page de connexion, ou empêcher un utilisateur connecté d'accéder à la page de login.
Définir des métadonnées sur les routes
// src/router.ts
const routes = [
{
path: ROUTES.HOME,
component: HomePage,
},
{
path: ROUTES.SHOW_DETAIL,
component: ShowDetailPage,
meta: { requiresAuth: true }, // Nécessite d'être connecté
},
]
Typer les métadonnées (TypeScript)
// src/router.ts (en haut du fichier)
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean
}
}
Implémenter le guard global
// src/router.ts
import { ref } from 'vue'
export const isAuthenticated = ref(false)
router.beforeEach((to) => {
if (to.meta.requiresAuth && !isAuthenticated.value) {
return ROUTES.HOME
}
return true
})
Le bouton dans le NavMenu importe et bascule directement la ref exportée depuis le router :
<!-- src/components/NavMenu.component.vue -->
<template>
<NSpace align="center" :size="8">
<RouterLink :to="ROUTES.HOME">
<NButton quaternary :type="route.path === ROUTES.HOME ? 'primary' : 'default'">
Accueil
</NButton>
</RouterLink>
<NButton v-if="isAuthenticated" type="default" @click="isAuthenticated = false">
Déconnexion
</NButton>
<NButton v-else type="primary" @click="isAuthenticated = true">
Se connecter
</NButton>
</NSpace>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { isAuthenticated, ROUTES } from '@/router'
const route = useRoute()
</script>