📍 Article principal du parcours : Vue 3 et la Composition API : le guide complet. Pour la vue d’ensemble, commencez par là.
Introduction
Jusqu’ici, NoteFlux — l’application de notes que nous construisons au fil de ce parcours — tient sur un seul écran. Mais une vraie application a plusieurs pages : la liste des notes, la fiche détaillée d’une note précise, un écran de réglages. Vous voulez que chacune ait sa propre URL, que le bouton « précédent » du navigateur fonctionne, et qu’un lien partagé ouvre directement la bonne note. C’est exactement le rôle de Vue Router, le routeur officiel de Vue. À la fin de ce tutoriel, NoteFlux sera une application à plusieurs vues, avec des adresses propres, une navigation fluide et un écran de réglages protégé.
🎯 Ce que vous allez apprendre
- Installer et brancher Vue Router sur une application Vue 3.
- Déclarer des routes statiques et des routes dynamiques avec paramètres (
/note/:id). - Naviguer par liens (
RouterLink) et par code (router.push). - Lire les paramètres et la requête d’URL avec
useRoute. - Protéger une route avec une garde de navigation et gérer une page « introuvable ».
- Charger les vues à la demande pour alléger le chargement initial.
🛠️ Ce que vous allez construire
Vous transformez NoteFlux en application à trois vues : une page d’accueil qui liste les notes, une page de détail accessible à l’adresse /note/42, et une page de réglages verrouillée tant qu’un code n’est pas saisi. Le tout avec une barre de navigation dont le lien actif se met en surbrillance automatiquement, et une page d’erreur élégante pour les adresses inconnues.
Prérequis
- Node.js 20 ou plus récent et un projet Vue 3 créé avec Vite (
npm create vue@latest). - Être à l’aise avec les composants, les props et les events. Test express : si vous savez créer un composant et lui passer une prop, vous êtes prêt ; sinon, lisez d’abord le tutoriel sur les composants, props et events.
- ⏱️ Temps estimé : environ 40 minutes.
Au moment d’écrire ces lignes, la version courante est Vue Router 5. Elle a intégré le routage par fichiers (l’ancien greffon unplugin-vue-router) directement dans le cœur, sans casser la compatibilité : tout le code écrit pour Vue Router 4 fonctionne tel quel. Nous utilisons ici la configuration explicite des routes, plus claire pour comprendre les mécanismes ; le routage par fichiers viendra naturellement ensuite.
Étape 1 — Installer et initialiser le routeur
Le routeur n’est pas inclus dans Vue par défaut ; c’est un paquet séparé. Si vous avez coché « Vue Router » lors de la création du projet, il est déjà là. Sinon, on l’ajoute en une commande. L’idée d’un routeur côté client est simple : intercepter les changements d’URL, et afficher le composant correspondant sans recharger la page.
# installe la version courante de Vue Router
npm install vue-router@5
On crée ensuite un fichier src/router/index.js qui déclare la correspondance entre chemins et composants. Le routeur a besoin d’un mode d’historique : createWebHistory produit de belles URL sans dièse (/note/42), en s’appuyant sur l’API History du navigateur.
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Accueil from '../views/Accueil.vue'
const routes = [
{ path: '/', name: 'accueil', component: Accueil },
]
export const router = createRouter({
history: createWebHistory(),
routes,
})
On déclare une seule route pour l’instant : la racine / qui affiche la vue Accueil. Le name est facultatif mais précieux : il permet de naviguer par nom plutôt que par chemin, ce qui évite de casser tous les liens le jour où vous changez une URL.
Étape 2 — Brancher le routeur et placer la vue
Deux choses à faire pour activer le routeur. D’abord, l’enregistrer sur l’application dans le point d’entrée. Ensuite, indiquer où afficher le composant de la route courante, grâce au composant RouterView : c’est l’emplacement où Vue Router injecte la vue active.
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import { router } from './router'
createApp(App).use(router).mount('#app')
Dans App.vue, on pose la barre de navigation et l’emplacement de rendu. RouterLink remplace la balise <a> : il navigue sans recharger la page et ajoute automatiquement une classe CSS au lien actif.
<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<nav>
<RouterLink to="/">Mes notes</RouterLink>
<RouterLink to="/reglages">Réglages</RouterLink>
</nav>
<main>
<RouterView />
</main>
</template>
<style scoped>
nav a.router-link-active { font-weight: 700; }
</style>
Lancez npm run dev : le lien « Mes notes » affiche la vue Accueil, et il apparaît en gras parce que Vue Router lui a appliqué la classe router-link-active. Vous avez un routeur qui tourne.
✅ Point d’étape — En cliquant sur les liens, l’URL change dans la barre d’adresse sans rechargement. Si la page se recharge entièrement, vous avez probablement utilisé une balise
<a href>au lieu deRouterLink.
Étape 3 — Une route dynamique pour la fiche d’une note
Chaque note doit avoir sa propre adresse, par exemple /note/42. On ne va pas écrire une route par note : on déclare un segment dynamique avec deux-points. Le :id capture n’importe quelle valeur et la rend disponible dans la vue.
// dans le tableau routes
{ path: '/note/:id', name: 'note', component: NoteDetail },
Dans la vue de détail, on lit ce paramètre avec useRoute, la fonction de la Composition API qui donne accès à la route courante. On l’utilise pour retrouver la bonne note dans notre liste.
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { notes } from '../data/notes'
const route = useRoute()
const note = computed(() =>
notes.find(n => n.id === Number(route.params.id))
)
</script>
<template>
<article v-if="note">
<h1>{{ note.titre }}</h1>
<p>{{ note.contenu }}</p>
</article>
<p v-else>Cette note n'existe pas.</p>
</template>
Le paramètre d’URL arrive toujours sous forme de chaîne ; d’où le Number() pour comparer avec un identifiant numérique. On enveloppe la recherche dans un computed pour qu’elle se recalcule si l’on navigue d’une note à l’autre. Le v-if/v-else gère proprement le cas d’un identifiant qui ne correspond à rien.
✅ Point d’étape — Tapez
/note/1dans la barre d’adresse : la fiche correspondante s’affiche. Changez l’identifiant : le contenu suit. Si rien ne change quand vous passez de/note/1à/note/2, c’est que vous avez oublié lecomputedou comparé une chaîne à un nombre.
Étape 4 — Naviguer par liens et par code
Depuis la liste, chaque note doit pointer vers sa fiche. On préfère la navigation par nom avec un objet : c’est robuste, car le jour où l’URL change, seul le fichier du routeur est touché. La syntaxe :to accepte un objet avec le name de la route et ses params.
<RouterLink
v-for="n in notes"
:key="n.id"
:to="{ name: 'note', params: { id: n.id } }">
{{ n.titre }}
</RouterLink>
Parfois, la navigation doit se déclencher depuis du code — après l’enregistrement d’une note, par exemple, on veut rediriger vers sa fiche. On utilise alors useRouter (le routeur, pas la route) et sa méthode push.
import { useRouter } from 'vue-router'
const router = useRouter()
function ouvrirNote(id) {
router.push({ name: 'note', params: { id } })
}
Retenez la distinction : useRoute (au singulier) donne la route actuelle et ses paramètres ; useRouter donne l’instance du routeur pour déclencher des navigations. C’est la confusion la plus fréquente chez les débutants.
Étape 5 — Charger les vues à la demande
Par défaut, toutes les vues sont incluses dans le paquet initial. Sur une application qui grossit, cela alourdit le premier chargement. La parade est le chargement paresseux : on remplace l’import statique du composant par une fonction qui l’importe dynamiquement. Vite découpe alors automatiquement le code, et la vue n’est téléchargée que lorsqu’on visite sa route.
const routes = [
{ path: '/', name: 'accueil', component: Accueil },
// chargée seulement quand on visite /note/:id
{ path: '/note/:id', name: 'note', component: () => import('../views/NoteDetail.vue') },
{ path: '/reglages', name: 'reglages', component: () => import('../views/Reglages.vue') },
]
La différence est invisible à l’usage mais bien réelle dans l’onglet « Réseau » des outils de développement : un fichier séparé se charge au moment d’ouvrir la fiche. Sur une connexion lente, c’est un gain direct sur le temps d’affichage de la page d’accueil.
Étape 6 — Protéger une route avec une garde
L’écran de réglages ne doit s’ouvrir qu’après saisie d’un code. Vue Router fournit des gardes de navigation : des fonctions appelées avant chaque changement de route, qui peuvent l’autoriser, l’annuler, ou rediriger. La garde globale beforeEach s’exécute pour toutes les navigations.
router.beforeEach((to) => {
if (to.meta.protege && !sessionStorage.getItem('deverrouille')) {
// redirige vers l'accueil au lieu d'ouvrir la page
return { name: 'accueil' }
}
return true
})
On marque la route à protéger avec un champ meta personnalisé, lu par la garde :
{ path: '/reglages', name: 'reglages',
component: () => import('../views/Reglages.vue'),
meta: { protege: true } },
La garde renvoie true pour laisser passer, un objet de route pour rediriger, ou false pour annuler. Ici, sans le drapeau deverrouille en session, toute tentative d’accès aux réglages renvoie à l’accueil. C’est le squelette de toute logique d’authentification réelle : on remplacera plus tard le sessionStorage par un vrai état d’utilisateur connecté, souvent stocké dans un store Pinia.
✅ Point d’étape — Cliquez sur « Réglages » sans déverrouiller : vous restez sur l’accueil. Exécutez
sessionStorage.setItem('deverrouille','1')dans la console, réessayez : la page s’ouvre.
Étape 7 — Gérer les adresses inconnues
Que se passe-t-il si quelqu’un tape /page-qui-nexiste-pas ? Sans route correspondante, RouterView reste vide. On ajoute une route « attrape-tout » en dernière position, avec une expression qui capture n’importe quel chemin restant.
{ path: '/:pathMatch(.*)*', name: 'introuvable',
component: () => import('../views/Introuvable.vue') },
La syntaxe :pathMatch(.*)* est la façon officielle, en Vue Router 4 et 5, de déclarer une route de repli. Toute URL non reconnue affiche désormais votre page d’erreur personnalisée plutôt qu’un écran blanc.
🐞 Pièges fréquents
| Symptôme / erreur | Cause probable | Correctif |
|---|---|---|
| La page se recharge à chaque clic | Balise <a href> au lieu de RouterLink |
Utiliser <RouterLink to="..."> |
route.params.id est une chaîne |
Les paramètres d’URL sont toujours des chaînes | Convertir avec Number() avant comparaison |
| La fiche ne change pas en passant de /note/1 à /note/2 | Le composant est réutilisé, le code ne réagit pas au changement de paramètre | Envelopper la lecture dans un computed ou un watch sur route.params |
404 du serveur en rechargeant /note/42 en production |
Le serveur ne connaît pas les routes du client | Configurer une réécriture vers index.html (mode historique HTML5) |
Confusion useRoute / useRouter |
Noms très proches, rôles opposés | useRoute = route actuelle ; useRouter = instance pour naviguer |
Réalités du terrain
Un piège mord presque tout le monde au premier déploiement. En développement, recharger /note/42 fonctionne ; une fois en ligne, la même URL renvoie une erreur 404 du serveur. La raison : avec le mode historique HTML5, ces chemins n’existent pas en tant que fichiers sur le serveur — c’est le routeur côté client qui les gère. La solution est de configurer le serveur pour qu’il renvoie toujours index.html, quelle que soit l’URL demandée. Sur un hébergement Apache, une règle de réécriture dans .htaccess suffit ; sur Nginx, une directive try_files. La documentation officielle de Vue Router fournit les recettes exactes pour chaque serveur. Si votre hébergement ne permet aucune configuration, l’alternative est le mode createWebHashHistory, qui place un dièse dans l’URL (/#/note/42) et fonctionne partout sans réglage serveur — au prix d’adresses moins élégantes.
✅ Récapitulatif
Vous venez de transformer NoteFlux en application à plusieurs vues. Vous savez déclarer des routes statiques et dynamiques, lire un paramètre d’URL avec useRoute, naviguer par lien et par code, alléger le chargement avec l’import dynamique, protéger une route avec beforeEach et gérer les adresses inconnues. Ce sont les briques de toute application Vue à plusieurs pages. L’étape logique suivante : faire communiquer ces vues entre elles via un état partagé, ce qui est précisément le rôle de Pinia.
🧾 Aide-mémoire
| Élément | Rôle |
|---|---|
createRouter / createWebHistory |
Créer le routeur et choisir le mode d’URL |
RouterLink / RouterView |
Lien de navigation / emplacement de rendu |
path: '/note/:id' |
Route avec paramètre dynamique |
useRoute() |
Route courante : route.params, route.query |
useRouter() |
Instance pour router.push(...) |
component: () => import(...) |
Chargement paresseux d’une vue |
router.beforeEach |
Garde de navigation globale |
/:pathMatch(.*)* |
Route attrape-tout (page introuvable) |
💪 À vous de jouer
Ajoutez un fil d’Ariane qui affiche « Accueil › Titre de la note » sur la page de détail, avec un lien cliquable vers l’accueil. Indice : lisez le titre depuis la note trouvée, et utilisez RouterLink pour le retour.
Voir une solution
<nav class="fil-ariane" v-if="note">
<RouterLink :to="{ name: 'accueil' }">Accueil</RouterLink>
<span> › {{ note.titre }}</span>
</nav>
Le v-if="note" évite d’afficher un fil d’Ariane vide quand l’identifiant ne correspond à aucune note.
Tutoriels frères
- Gérer l’état avec Pinia — partager l’état de connexion et les notes entre toutes les vues.
- Nuxt : SSR et déploiement — le routage par fichiers et le rendu serveur.
Pour aller plus loin
- 🔝 Retour au guide : Vue 3 et la Composition API.
- Documentation officielle de Vue Router — gardes, transitions, routes nommées et recettes serveur.
FAQ
Vue Router 4 ou 5 ?
La version 5 est la version courante et n’introduit aucune rupture par rapport à la 4 : votre code existant fonctionne sans changement. La nouveauté principale est l’intégration du routage par fichiers dans le cœur. Pour un nouveau projet, installez la 5.
Faut-il nommer toutes les routes ?
Ce n’est pas obligatoire, mais fortement conseillé. Naviguer par nom plutôt que par chemin rend le code résistant aux changements d’URL et évite les liens cassés.
Quelle différence entre params et query ?
Les params font partie du chemin (/note/42) et sont déclarés dans la route. La query est la partie après le point d’interrogation (/notes?tri=date) et n’a pas besoin d’être déclarée. On lit les deux via useRoute.
Le routage par fichiers remplace-t-il tout ceci ?
Il automatise la déclaration des routes à partir de l’arborescence des fichiers, mais les concepts restent identiques : paramètres, gardes, navigation. Comprendre la configuration explicite, comme ici, reste la meilleure base avant d’adopter l’automatisme.