📦 Installation CDN & createRouter

Vue Router est la bibliothèque officielle de navigation pour Vue 3. Dans un contexte CDN (sans serveur, sans bundler), on utilise les versions IIFE chargées via <script>.

Chargement via CDN

<!-- Vue 3 en premier, puis Vue Router -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vue-router@4/dist/vue-router.global.js"></script>

<script>
// Destructurer depuis l'objet global VueRouter
const { createRouter, createWebHashHistory, RouterLink, RouterView,
        useRouter, useRoute } = VueRouter;

const { createApp, ref, computed } = Vue;
</script>

Pourquoi createWebHashHistory en CDN ?

Il existe deux modes d'historique dans Vue Router :

  • createWebHistory() — URLs propres (/articles/1) mais nécessite une configuration serveur pour rediriger toutes les URLs vers index.html
  • createWebHashHistory() — URLs avec hash (/#/articles/1) — tout se passe côté client, aucun serveur requis
⚠️ En CDN (fichiers ouverts directement depuis le disque ou un serveur statique simple), utilisez toujours createWebHashHistory(). Avec createWebHistory(), rafraîchir la page donnera une erreur 404.

Création du router

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    { path: '/', component: AccueilPage },
    { path: '/about', component: AboutPage },
  ]
});

const app = createApp(AppRoot);
app.use(router);   // enregistrer le plugin Vue Router
app.mount('#app');
💡 app.use(router) enregistre globalement <RouterLink> et <RouterView> comme composants, et injecte useRouter() / useRoute() dans toute l'application.

🗺️ Définir les routes

Chaque route est un objet avec au minimum path et component. On peut aussi lui donner un name pour naviguer sans dépendre du chemin.

Structure d'un tableau de routes

const routes = [
  // Route simple
  { path: '/', component: HomePage },

  // Route nommée
  { path: '/articles', name: 'articles', component: ArticlesPage },

  // Route avec paramètre dynamique
  { path: '/articles/:id', name: 'article', component: ArticleDetailPage },

  // Route imbriquée (children)
  {
    path: '/profil',
    component: ProfilLayout,
    children: [
      { path: '',      component: ProfilInfos },
      { path: 'edit',  component: ProfilEdit },
    ]
  },

  // Route 404 — doit être en dernier !
  { path: '/:pathMatch(.*)*', name: 'notFound', component: NotFoundPage },
];

Propriétés importantes d'une route

PropriétéTypeDescription
pathstringChemin URL. Commence toujours par /
componentobjectLe composant Vue à afficher
namestringNom unique pour la navigation nommée
metaobjectDonnées personnalisées (ex: { requiresAuth: true })
redirectstring/objectRediriger vers un autre chemin
childrenarrayRoutes imbriquées

Composants inline (CDN)

En CDN, les composants sont définis comme des objets avec une option template :

const HomePage = {
  template: `
    <div>
      <h1>Accueil</h1>
      <p>Bienvenue sur mon site !</p>
    </div>
  `
};

const AboutPage = {
  template: `<div><h1>À propos</h1></div>`
};

🔗 RouterLink & RouterView

Ces deux composants sont les piliers de Vue Router côté template.

RouterView

<RouterView /> est le slot de rendu — il affiche le composant correspondant à la route active. On le place là où le contenu doit changer selon la navigation.

const AppRoot = {
  // RouterView est enregistré globalement par app.use(router)
  template: `
    <div>
      <nav>
        <!-- RouterLink : navigation déclarative -->
        <RouterLink to="/">Accueil</RouterLink>
        <RouterLink to="/articles">Articles</RouterLink>
        <RouterLink to="/about">À propos</RouterLink>
      </nav>

      <main>
        <!-- Le composant de la route active s'affiche ici -->
        <RouterView />
      </main>
    </div>
  `
};

RouterLink vs <a href>

RouterLinka href
Navigation sans rechargement de pageRecharge toute la page
Ajoute automatiquement la classe router-link-activePas de classe active
Prend en compte le mode history/hashURL brute uniquement
Compatible avec les routes nomméesChemin brut uniquement

RouterLink avancé

<!-- Vers une route nommée -->
<RouterLink :to="{ name: 'article', params: { id: 42 } }">Lire</RouterLink>

<!-- Avec query string -->
<RouterLink :to="{ path: '/articles', query: { page: 2 } }">Page 2</RouterLink>

<!-- Classe active personnalisée -->
<RouterLink to="/about" active-class="mon-lien-actif">À propos</RouterLink>
router-link-active est ajoutée si le chemin commence par le to. router-link-exact-active est ajoutée seulement si le chemin est exactement identique.

🔧 Paramètres de route

Les paramètres dynamiques permettent de créer des routes génériques comme /article/:id:id capture n'importe quelle valeur.

Définir un paramètre

const routes = [
  { path: '/article/:id', component: ArticleDetail },
  { path: '/user/:username/posts/:postId', component: UserPost },
];

Lire les paramètres avec useRoute()

const ArticleDetail = {
  setup() {
    const route = useRoute();

    // Paramètre de route — /article/42 => params.id === "42"
    const articleId = computed(() => route.params.id);

    // Query string — /article/42?lang=fr => query.lang === "fr"
    const langue = computed(() => route.query.lang || 'fr');

    // Hash — /article/42#commentaires => hash === "#commentaires"
    const section = computed(() => route.hash);

    return { articleId, langue, section };
  },
  template: `
    <div>
      <h1>Article #{{ articleId }}</h1>
      <p>Langue : {{ langue }}</p>
    </div>
  `
};

Réagir aux changements de paramètre

Si on navigue de /article/1 à /article/2, le composant est réutilisé (pas détruit/recréé). Il faut observer les changements :

const { watch, ref } = Vue;

const ArticleDetail = {
  setup() {
    const route = useRoute();
    const article = ref(null);

    // Watcher sur route.params pour réagir aux changements
    watch(() => route.params.id, (newId) => {
      chargerArticle(newId);
    }, { immediate: true }); // immediate: true pour le chargement initial

    async function chargerArticle(id) {
      // Simuler un fetch
      article.value = { id, titre: `Article numéro ${id}` };
    }

    return { article };
  },
  template: `
    <div v-if="article">
      <h1>{{ article.titre }}</h1>
    </div>
  `
};
💡 route.params.id est toujours une chaîne — si vous avez besoin d'un nombre, utilisez Number(route.params.id).

⚡ Navigation programmatique

Parfois on veut naviguer depuis du code JavaScript (après un login, après soumission d'un formulaire...) plutôt que depuis un <RouterLink>. On utilise alors useRouter().

useRouter() — méthodes principales

const MonComposant = {
  setup() {
    const router = useRouter();

    function allerAccueil() {
      // Naviguer vers un chemin
      router.push('/');
    }

    function allerProfil(userId) {
      // Naviguer avec un objet (route nommée + params)
      router.push({ name: 'profil', params: { id: userId } });
    }

    function allerRechercheAvecFiltres(q) {
      // Naviguer avec query string
      router.push({ path: '/recherche', query: { q, page: 1 } });
    }

    function remplacerSansHistorique() {
      // replace() : ne pas ajouter à l'historique (le bouton "précédent" ne revient pas)
      router.replace('/dashboard');
    }

    function retour() {
      router.go(-1);    // Équivalent de window.history.back()
    }

    function avancer() {
      router.go(1);     // Équivalent de window.history.forward()
    }

    return { allerAccueil, allerProfil, allerRechercheAvecFiltres,
             remplacerSansHistorique, retour, avancer };
  },
  template: `
    <div>
      <button @click="allerAccueil">Accueil</button>
      <button @click="retour">← Retour</button>
    </div>
  `
};

push() vs replace()

MéthodeHistoriqueCas d'usage
router.push()Ajoute une entréeNavigation normale
router.replace()Remplace l'entrée actuelleLogin → Dashboard (on ne veut pas revenir en arrière)
router.go(n)Navigation relativeBoutons précédent/suivant

🛡️ Navigation Guards

Les guards interceptent chaque navigation avant qu'elle ne se produise. Ils permettent de protéger des routes, de journaliser, ou de faire des redirections conditionnelles.

Guard global — beforeEach

// Enregistrer un guard APRÈS la création du router
router.beforeEach((to, from) => {
  // to   : la route de destination
  // from : la route de départ

  console.log(`Navigation : ${from.path} → ${to.path}`);

  // Retourner true (ou rien) : la navigation continue
  // Retourner false          : la navigation est annulée
  // Retourner une route      : redirection

  return true;
});

Routes protégées avec meta.requiresAuth

// Marquer une route comme protégée via meta
const routes = [
  { path: '/',          component: HomePage },
  { path: '/login',     component: LoginPage },
  {
    path: '/dashboard',
    component: DashboardPage,
    meta: { requiresAuth: true }     // ← méta-donnée personnalisée
  },
  {
    path: '/admin',
    component: AdminPage,
    meta: { requiresAuth: true, role: 'admin' }
  },
];

// Guard global qui vérifie l'authentification
const estAuthentifie = ref(false); // En pratique : lire depuis un store Pinia

router.beforeEach((to, from) => {
  if (to.meta.requiresAuth && !estAuthentifie.value) {
    // Rediriger vers login en mémorisant la destination
    return { path: '/login', query: { redirect: to.fullPath } };
  }
  // Sinon laisser passer
});

afterEach — journalisation

// afterEach : s'exécute APRÈS chaque navigation réussie
router.afterEach((to, from) => {
  // Pas de contrôle sur la navigation (trop tard)
  // Utile pour : analytics, scroll-to-top, title de page
  document.title = to.meta.title || 'Mon Application';
  window.scrollTo(0, 0);
});
✅ Les guards beforeEach reçoivent aussi un 3ème paramètre next() en ancienne API. En Vue Router 4 avec Composition API, retourner une valeur est suffisant — next() est facultatif.
✏️ Exercices V07 ▶ Mini-projet Blog Suivant : Pinia →