Skip to main content

6. Routing et Navigation Guards

Gérer la navigation dans une SPA Vue 3 avec Vue Router et les 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 renvoyer index.html pour 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> 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>

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)

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>