Retour au cours

frontend / vuejs

Vue Router — bases

Leçon 121 exercice

Explication

Ce que vous allez apprendre

  • Installer et configurer Vue Router avec createWebHistory pour des URLs propres
  • Déclarer des routes avec des segments dynamiques et des routes imbriquées
  • Utiliser <router-link> pour naviguer déclarativement et <router-view> pour afficher la page courante
  • Naviguer de façon programmatique avec useRouter() et lire l'état de la route avec useRoute()
  • Découper automatiquement le bundle JavaScript par page grâce au lazy loading des routes

Dans quel contexte ?

Une application affiche une page d'accueil, une page de détail produit dont l'identifiant varie dans l'URL, et un espace profil avec des sous-sections. Sans Vue Router, il faudrait bricoler manuellement l'affichage conditionnel de "pages" en fonction d'un état applicatif, sans jamais bénéficier de vraies URLs partageables ni du bouton précédent/suivant du navigateur. Vue Router résout tout ça nativement.

D'abord, transformer une SPA en application multi-pages

Sans Vue Router, une application Vue est en réalité une seule "page" qui affiche différents composants selon un état interne. Vue Router relie chaque URL à un composant précis, rendant possible la navigation par lien direct, le bouton précédent du navigateur, et le partage d'une URL vers une page exacte de l'application.

createWebHistory() génère des URLs propres (/produits/3) plutôt que des URLs avec un dièse (/#/produits/3), mais nécessite en contrepartie une configuration serveur qui redirige toute requête vers index.html, pour que Vue Router puisse ensuite interpréter l'URL côté client.

Prérequis

Il faut avoir compris les composants et leur communication (leçons 5 et 8) : chaque route associe une URL à un composant, affiché comme n'importe quel autre.

Ensuite, capturer une information directement depuis l'URL

Un segment dynamique comme :id dans /produits/:id devient accessible via route.params.id, ou directement en prop du composant si la route est déclarée avec props: true — une option qui évite de coupler le composant directement au routeur, le rendant plus facilement testable en isolation.

ÉlémentRôle
<router-link to="/">Génère un lien de navigation sans recharger la page
<router-view />Emplacement où s'affiche le composant de la route active
route.paramsSegments dynamiques capturés dans l'URL
route.queryParamètres de requête après le ?

Il reste à structurer des sections avec des sous-pages

Les routes imbriquées (children: [...]) permettent à une route parente comme /profil de définir un layout commun, dans lequel différentes sous-routes (/profil, /profil/securite) affichent chacune leur propre contenu à l'intérieur d'un <router-view> imbriqué du composant parent.

Astuce

Une route "catch-all" ({ path: '/:pathMatch(.*)*', component: NotFound }) placée en dernière position capture toute URL qui ne correspond à aucune route déclarée, offrant une page 404 personnalisée plutôt qu'un écran blanc.

Maintenant, naviguer depuis du code plutôt que depuis un lien

useRouter().push({ name: 'produit-detail', params: { id } }) déclenche une navigation programmatique, utile après une action (soumission de formulaire réussie, par exemple) plutôt qu'un simple clic sur un lien. useRoute(), à ne pas confondre avec useRouter(), donne accès en LECTURE à l'état de la route actuelle.

Piège fréquent

Confondre useRouter() (l'instance du routeur, pour naviguer) et useRoute() (l'état de la route courante, pour lire) est une erreur fréquente chez les débutants — les deux noms se ressemblent beaucoup mais n'ont pas du tout le même rôle.

Maintenant que la navigation de base fonctionne, la prochaine leçon aborde un besoin presque systématique en production : contrôler, annuler ou rediriger une navigation selon des règles métier, avec les navigation guards.

Commandes & code

Vue Router — bases

Le routing officiel pour transformer une SPA en application multi-pages avec URLs propres.

bash
npm install vue-router@4
js
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import AccueilVue from '../pages/Accueil.vue'
import ProduitDetail from '../pages/ProduitDetail.vue'
import ProfilVue from '../pages/Profil.vue'

const routes = [
  { path: '/', name: 'accueil', component: AccueilVue },

  // segment dynamique :id -> accessible via route.params.id
  { path: '/produits/:id', name: 'produit-detail', component: ProduitDetail, props: true },

  // routes imbriquées
  {
    path: '/profil',
    component: ProfilVue,
    children: [
      { path: '', name: 'profil-vue', component: () => import('../pages/ProfilVue.vue') },
      { path: 'securite', name: 'profil-securite', component: () => import('../pages/ProfilSecurite.vue') },
    ],
  },

  // route "catch-all" 404
  { path: '/:pathMatch(.*)*', name: 'not-found', component: () => import('../pages/NotFound.vue') },
]

export const router = createRouter({
  history: createWebHistory(),   // URLs propres (/produits/3) plutôt que /#/produits/3
  routes,
  scrollBehavior(to, from, savedPosition) {
    return savedPosition || { top: 0 } // remonte en haut à chaque navigation, sauf retour arrière
  },
})
js
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { router } from './router'

createApp(App).use(router).mount('#app')
vue
<!-- App.vue -->
<template>
  <nav>
    <!-- router-link génère un <a> et gère le clic sans recharger la page -->
    <router-link to="/">Accueil</router-link>
    <router-link :to="{ name: 'produit-detail', params: { id: 42 } }">Produit #42</router-link>
    <!-- classe .router-link-active ajoutée automatiquement sur le lien actif -->
  </nav>

  <!-- Emplacement où le composant de la route courante est rendu -->
  <router-view />
</template>
vue
<!-- pages/ProduitDetail.vue -->
<script setup>
// props: true dans la config de route -> le param :id arrive directement en prop, pas via useRoute()
defineProps({ id: String })
</script>

<template>
  <h1>Produit #{{ id }}</h1>
</template>
js
// Navigation programmatique
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()  // pour naviguer
const route = useRoute()    // pour lire l'état courant (params, query, path)

function allerAuProduit(id) {
  router.push({ name: 'produit-detail', params: { id } })
}

function allerEnArriere() {
  router.back()
}

console.log(route.params.id, route.query.tri) // ex: /produits/3?tri=prix

Résumé

  • createWebHistory() pour des URLs propres (nécessite une config serveur qui redirige tout vers index.html).
  • <router-link> pour naviguer déclarativement, useRouter()/router.push() pour naviguer en JS.
  • Les segments dynamiques (:id) se récupèrent via useRoute().params ou en prop (props: true).
  • Le "lazy loading" de route (() => import('...')) découpe le bundle par page automatiquement.

Exercices pratiques

1 disponible
1

Mission : un identifiant produit introuvable en prop

Objectif : Diagnostiquer une confusion entre useRoute et useRouter, puis corriger une configuration de route pour exposer proprement un paramètre dynamique.

Contexte

La page ProduitDetail.vue est censée recevoir l'id du produit en prop grâce à props: true sur la route /produits/:id, mais un développeur a écrit à l'intérieur du composant const route = useRouter(); console.log(route.params.id) pour déboguer, et ça plante avec une erreur indiquant que params est undefined. Par ailleurs, la route /produits/:id a été déclarée SANS props: true dans le fichier de router, alors que le composant utilise defineProps({ id: String }).

Résoudre l’exercice →