Creer un systeme de routage personnalise avec Vue.js et les composants dynamiques

Apprenez a construire un routeur Vue.js de zero avec les composants dynamiques. Gerez la navigation et les routes 404 sans dependances externes.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 9 min read
Creer un systeme de routage personnalise avec Vue.js et les composants dynamiques
Table of Contents

Creer un systeme de routage personnalise avec Vue.js

Introduction au routage cote client (SPA)

Les Single Page Applications (SPA) ont revolutionne la maniere dont nous concevons les applications web modernes. Contrairement aux applications traditionnelles ou chaque navigation entraine un rechargement complet de la page depuis le serveur, les SPA chargent une seule page HTML puis mettent a jour dynamiquement le contenu en fonction des interactions de l’utilisateur.

Le routage cote client est le mecanisme qui permet cette magie. Lorsqu’un utilisateur clique sur un lien, au lieu d’envoyer une requete au serveur, JavaScript intercepte cette action et met a jour l’interface utilisateur localement. L’URL change dans la barre d’adresse du navigateur, mais aucun rechargement de page ne se produit.

Pourquoi le routage est-il essentiel ?

Le routage dans une SPA remplit plusieurs fonctions critiques :

  1. Navigation sans rechargement : L’utilisateur peut naviguer entre differentes “pages” sans attendre de chargement
  2. Partage de liens : Chaque vue possede sa propre URL, permettant de partager des liens directs
  3. Historique de navigation : Les boutons Precedent/Suivant du navigateur fonctionnent correctement
  4. SEO et accessibilite : Les moteurs de recherche peuvent indexer les differentes pages de l’application

Vue Router vs Routeur personnalise

Vue.js propose Vue Router, une solution de routage officielle, complete et robuste. Cependant, comprendre comment construire un routeur de zero vous permettra de :

  • Maitriser les concepts fondamentaux du routage SPA
  • Creer des solutions legeres pour des projets simples
  • Debugger plus efficacement les problemes de routage
  • Personnaliser le comportement de navigation selon vos besoins specifiques

Dans cet article, nous allons construire un systeme de routage complet, etape par etape.


Configuration runtimeCompiler : Pourquoi est-ce necessaire ?

Avant de plonger dans le code, il est crucial de comprendre une configuration fondamentale de Vue.js : le runtime compiler.

Les deux builds de Vue.js

Vue.js est disponible en deux versions :

VersionDescriptionTaille
Runtime onlyNe peut pas compiler les templates a la volee~22KB
Runtime + CompilerPeut compiler les templates dans le navigateur~32KB

Par defaut, Vue CLI utilise la version “runtime only” pour des raisons de performance. Cependant, lorsque vous definissez des composants avec l’option template (comme nous le ferons), vous avez besoin du compilateur.

Configuration dans vue.config.js

Pour activer le compilateur, ajoutez cette configuration :

// vue.config.js
module.exports = {
  // Active la compilation des templates a la volee
  runtimeCompiler: true,

  // Autres configurations optionnelles
  productionSourceMap: false,

  configureWebpack: {
    resolve: {
      alias: {
        // Alias explicite vers la version complete
        'vue$': 'vue/dist/vue.esm-bundler.js'
      }
    }
  }
};

Configuration avec Vite

Si vous utilisez Vite au lieu de Vue CLI :

// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      vue: 'vue/dist/vue.esm-bundler.js'
    }
  }
});

Quand utiliser le runtime compiler ?

Le runtime compiler est necessaire lorsque :

  • Vous definissez des templates sous forme de chaines de caracteres
  • Vous creez des composants dynamiques a la volee
  • Vous recevez des templates depuis une API externe
  • Vous construisez un routeur personnalise avec des composants inline

Le composant dynamique avec :is

Le coeur de notre systeme de routage repose sur une fonctionnalite puissante de Vue.js : le composant dynamique.

Syntaxe de base

L’element special <component> combine avec l’attribut :is permet de rendre differents composants de maniere dynamique :

<template>
  <component :is="currentComponent"></component>
</template>

<script>
export default {
  data() {
    return {
      currentComponent: 'MonComposant'
    };
  }
};
</script>

Differentes valeurs pour :is

L’attribut :is accepte plusieurs types de valeurs :

// 1. Nom du composant enregistre (string)
currentComponent: 'mon-composant'

// 2. Objet de definition du composant
currentComponent: {
  template: '<div>Contenu dynamique</div>',
  data() { return { message: 'Hello' }; }
}

// 3. Reference a un composant importe
import MonComposant from './MonComposant.vue';
currentComponent: MonComposant

// 4. Composant asynchrone
currentComponent: () => import('./MonComposant.vue')

Preservation de l’etat avec keep-alive

Par defaut, les composants sont detruits et recrees lors du changement. Pour preserver leur etat :

<template>
  <keep-alive>
    <component :is="currentView"></component>
  </keep-alive>
</template>

Cela est particulierement utile pour :

  • Formulaires partiellement remplis
  • Positions de scroll
  • Donnees en cache

Creation des composants de vue

Maintenant, creons les composants qui representeront nos differentes pages.

Structure des composants de page

// Composant pour la page d'accueil
const HomeView = {
  name: 'home-view',
  template: `
    <div class="page home">
      <h1>Bienvenue sur notre site</h1>
      <p>Decouvrez notre selection de films exceptionnels.</p>
      <ul class="movie-list">
        <li v-for="movie in movies" :key="movie.id">
          <a :href="movie.path" @click.prevent="navigate(movie.path)">
            {{ movie.title }}
          </a>
        </li>
      </ul>
    </div>
  `,
  data() {
    return {
      movies: [
        { id: 1, title: 'Dunkirk', path: '/dunkirk' },
        { id: 2, title: 'Interstellar', path: '/interstellar' },
        { id: 3, title: 'Inception', path: '/inception' }
      ]
    };
  },
  methods: {
    navigate(path) {
      window.dispatchEvent(new CustomEvent('navigate', { detail: path }));
    }
  }
};

// Composant pour le film Dunkirk
const DunkirkBlurb = {
  name: 'dunkirk-blurb',
  template: `
    <div class="page movie">
      <button @click="goBack" class="back-btn">← Retour</button>
      <h2>Dunkirk</h2>
      <div class="movie-meta">
        <span class="year">2017</span>
        <span class="director">Christopher Nolan</span>
        <span class="rating">8.4/10</span>
      </div>
      <p class="movies__description">
        L'evacuation miraculeuse des soldats allies de Belgique,
        Grande-Bretagne, Canada et France, qui etaient encercles
        par l'armee allemande sur les plages et le port de Dunkerque,
        en France, pendant la bataille de France lors de la Seconde
        Guerre mondiale.
      </p>
      <div class="movie-details">
        <h3>Points forts</h3>
        <ul>
          <li>Cinematographie immersive</li>
          <li>Bande sonore de Hans Zimmer</li>
          <li>Narration non-lineaire</li>
        </ul>
      </div>
    </div>
  `,
  methods: {
    goBack() {
      window.history.back();
    }
  }
};

// Composant pour le film Interstellar
const InterstellarBlurb = {
  name: 'interstellar-blurb',
  template: `
    <div class="page movie">
      <button @click="goBack" class="back-btn">← Retour</button>
      <h2>Interstellar</h2>
      <div class="movie-meta">
        <span class="year">2014</span>
        <span class="director">Christopher Nolan</span>
        <span class="rating">8.7/10</span>
      </div>
      <p class="movies__description">
        Interstellar raconte les aventures d'un groupe d'explorateurs
        qui utilisent un trou de ver recemment decouvert pour depasser
        les limitations du voyage spatial humain et conquerir les vastes
        distances impliquees dans un voyage interstellaire.
      </p>
      <div class="movie-details">
        <h3>Themes explores</h3>
        <ul>
          <li>Amour et temps</li>
          <li>Relativite et physique</li>
          <li>Survie de l'humanite</li>
        </ul>
      </div>
    </div>
  `,
  methods: {
    goBack() {
      window.history.back();
    }
  }
};

// Composant pour le film Inception
const InceptionBlurb = {
  name: 'inception-blurb',
  template: `
    <div class="page movie">
      <button @click="goBack" class="back-btn">← Retour</button>
      <h2>Inception</h2>
      <div class="movie-meta">
        <span class="year">2010</span>
        <span class="director">Christopher Nolan</span>
        <span class="rating">8.8/10</span>
      </div>
      <p class="movies__description">
        Un voleur specialise dans l'extraction de secrets du subconscient
        pendant l'etat de reve se voit offrir la chance de voir ses
        crimes effaces en echange de l'implantation d'une idee dans
        l'esprit d'un PDG.
      </p>
      <div class="movie-details">
        <h3>Niveaux de reve</h3>
        <ul>
          <li>Reve niveau 1 : La ville</li>
          <li>Reve niveau 2 : L'hotel</li>
          <li>Reve niveau 3 : La forteresse</li>
          <li>Les limbes</li>
        </ul>
      </div>
    </div>
  `,
  methods: {
    goBack() {
      window.history.back();
    }
  }
};

Configuration des routes

Definissons maintenant notre table de routage :

// Configuration des routes
const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView,
    meta: {
      title: 'Accueil',
      requiresAuth: false
    }
  },
  {
    path: '/dunkirk',
    name: 'dunkirk',
    component: DunkirkBlurb,
    meta: {
      title: 'Dunkirk',
      requiresAuth: false
    }
  },
  {
    path: '/interstellar',
    name: 'interstellar',
    component: InterstellarBlurb,
    meta: {
      title: 'Interstellar',
      requiresAuth: false
    }
  },
  {
    path: '/inception',
    name: 'inception',
    component: InceptionBlurb,
    meta: {
      title: 'Inception',
      requiresAuth: false
    }
  }
];

Creation du composant Router-View

Le composant router-view est le coeur de notre systeme. Il gere l’affichage du composant correspondant a l’URL actuelle.

const RouterView = {
  name: 'router-view',
  template: `
    <transition :name="transitionName" mode="out-in">
      <component :is="currentView" :key="currentPath"></component>
    </transition>
  `,
  data() {
    return {
      currentView: null,
      currentPath: window.location.pathname,
      transitionName: 'fade'
    };
  },

  computed: {
    // Trouve l'objet route correspondant au chemin actuel
    currentRoute() {
      return routes.find(route => route.path === this.currentPath);
    }
  },

  methods: {
    // Obtient l'objet route pour un chemin donne
    getRouteObject(path = this.currentPath) {
      return routes.find(route => route.path === path);
    },

    // Met a jour la vue actuelle
    updateView() {
      const route = this.getRouteObject();

      if (route === undefined) {
        // Route non trouvee : afficher la page 404
        this.currentView = this.getNotFoundComponent();
      } else {
        this.currentView = route.component;
        // Mettre a jour le titre de la page
        if (route.meta && route.meta.title) {
          document.title = `${route.meta.title} | Mon Application`;
        }
      }
    },

    // Composant 404 personnalise
    getNotFoundComponent() {
      return {
        template: `
          <div class="page not-found">
            <h1>404</h1>
            <h2>Page non trouvee</h2>
            <p>Desolee, la page que vous recherchez n'existe pas.</p>
            <button @click="goHome" class="btn-primary">
              Retourner a l'accueil
            </button>
          </div>
        `,
        methods: {
          goHome() {
            window.dispatchEvent(new CustomEvent('navigate', { detail: '/' }));
          }
        }
      };
    },

    // Navigue vers un nouveau chemin
    navigateTo(path) {
      if (path === this.currentPath) return;

      // Mettre a jour l'historique du navigateur
      window.history.pushState({ path }, '', path);
      this.currentPath = path;
      this.updateView();
    }
  },

  created() {
    // Initialiser la vue au chargement
    this.updateView();

    // Ecouter les evenements de navigation personnalises
    window.addEventListener('navigate', (event) => {
      this.navigateTo(event.detail);
    });

    // Ecouter les changements d'historique (bouton retour/avant)
    window.addEventListener('popstate', (event) => {
      this.currentPath = window.location.pathname;
      this.updateView();
    });
  },

  beforeUnmount() {
    // Nettoyer les ecouteurs d'evenements
    window.removeEventListener('navigate', this.navigateTo);
    window.removeEventListener('popstate', this.updateView);
  }
};

Gestion de l’historique avec History API

L’API History du navigateur est essentielle pour creer une experience de navigation fluide.

Les methodes principales

// Ajouter une nouvelle entree dans l'historique
window.history.pushState(state, title, url);

// Remplacer l'entree actuelle
window.history.replaceState(state, title, url);

// Naviguer dans l'historique
window.history.back();    // Page precedente
window.history.forward(); // Page suivante
window.history.go(-2);    // Deux pages en arriere

Implementation avancee avec etat

const NavigationManager = {
  // Pile d'historique locale pour tracking avance
  historyStack: [],
  currentIndex: -1,

  // Naviguer vers une nouvelle page
  push(path, state = {}) {
    const fullState = {
      path,
      ...state,
      timestamp: Date.now(),
      scrollPosition: { x: window.scrollX, y: window.scrollY }
    };

    // Supprimer les entrees futures si on navigue depuis le milieu
    if (this.currentIndex < this.historyStack.length - 1) {
      this.historyStack = this.historyStack.slice(0, this.currentIndex + 1);
    }

    this.historyStack.push(fullState);
    this.currentIndex++;

    window.history.pushState(fullState, '', path);

    // Scroll vers le haut
    window.scrollTo(0, 0);
  },

  // Remplacer l'entree actuelle
  replace(path, state = {}) {
    const fullState = {
      path,
      ...state,
      timestamp: Date.now()
    };

    if (this.historyStack.length > 0) {
      this.historyStack[this.currentIndex] = fullState;
    }

    window.history.replaceState(fullState, '', path);
  },

  // Gerer l'evenement popstate
  handlePopState(event) {
    if (event.state && event.state.scrollPosition) {
      // Restaurer la position de scroll
      window.scrollTo(
        event.state.scrollPosition.x,
        event.state.scrollPosition.y
      );
    }
  },

  // Verifier si on peut revenir en arriere
  canGoBack() {
    return this.currentIndex > 0;
  },

  // Verifier si on peut aller en avant
  canGoForward() {
    return this.currentIndex < this.historyStack.length - 1;
  }
};

// Initialiser l'ecouteur
window.addEventListener('popstate', NavigationManager.handlePopState.bind(NavigationManager));

Gestion des routes 404

Une bonne gestion des erreurs 404 ameliore significativement l’experience utilisateur.

Composant NotFound complet

const NotFoundView = {
  name: 'not-found-view',
  template: `
    <div class="not-found-container">
      <div class="error-code">
        <span class="four">4</span>
        <span class="zero">0</span>
        <span class="four">4</span>
      </div>

      <h1>Page non trouvee</h1>
      <p class="error-message">
        La page <code>{{ attemptedPath }}</code> n'existe pas ou a ete deplacee.
      </p>

      <div class="suggestions">
        <h3>Voici quelques suggestions :</h3>
        <ul>
          <li>Verifiez l'orthographe de l'URL</li>
          <li>La page a peut-etre ete deplacee ou supprimee</li>
          <li>Utilisez la navigation ci-dessous</li>
        </ul>
      </div>

      <div class="navigation-options">
        <button @click="goHome" class="btn btn-primary">
          Accueil
        </button>
        <button @click="goBack" class="btn btn-secondary" v-if="canGoBack">
          Page precedente
        </button>
      </div>

      <div class="search-suggestion">
        <p>Vous cherchez peut-etre :</p>
        <ul class="suggested-routes">
          <li v-for="route in suggestedRoutes" :key="route.path">
            <a :href="route.path" @click.prevent="navigate(route.path)">
              {{ route.meta.title }}
            </a>
          </li>
        </ul>
      </div>
    </div>
  `,
  data() {
    return {
      attemptedPath: window.location.pathname,
      canGoBack: window.history.length > 1
    };
  },
  computed: {
    suggestedRoutes() {
      return routes.filter(r => r.path !== '/').slice(0, 5);
    }
  },
  methods: {
    goHome() {
      window.dispatchEvent(new CustomEvent('navigate', { detail: '/' }));
    },
    goBack() {
      window.history.back();
    },
    navigate(path) {
      window.dispatchEvent(new CustomEvent('navigate', { detail: path }));
    }
  }
};

Styles CSS pour la page 404

.not-found-container {
  min-height: 60vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  text-align: center;
}

.error-code {
  font-size: 8rem;
  font-weight: bold;
  margin-bottom: 1rem;
}

.error-code .four {
  color: #3498db;
}

.error-code .zero {
  color: #e74c3c;
  animation: bounce 1s ease infinite;
}

@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-20px); }
}

.error-message code {
  background: #f4f4f4;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  font-family: monospace;
}

.navigation-options {
  display: flex;
  gap: 1rem;
  margin: 2rem 0;
}

.suggested-routes {
  list-style: none;
  padding: 0;
}

.suggested-routes li {
  margin: 0.5rem 0;
}

.suggested-routes a {
  color: #3498db;
  text-decoration: none;
  transition: color 0.2s;
}

.suggested-routes a:hover {
  color: #2980b9;
  text-decoration: underline;
}

Transitions entre routes

Les transitions ameliorent l’experience utilisateur en rendant la navigation plus fluide.

Configuration des transitions CSS

/* Transition Fade */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

/* Transition Slide */
.slide-enter-active,
.slide-leave-active {
  transition: transform 0.3s ease, opacity 0.3s ease;
}

.slide-enter-from {
  transform: translateX(100%);
  opacity: 0;
}

.slide-leave-to {
  transform: translateX(-100%);
  opacity: 0;
}

/* Transition Scale */
.scale-enter-active,
.scale-leave-active {
  transition: transform 0.3s ease, opacity 0.3s ease;
}

.scale-enter-from {
  transform: scale(0.95);
  opacity: 0;
}

.scale-leave-to {
  transform: scale(1.05);
  opacity: 0;
}

Transitions dynamiques basees sur la direction

const RouterViewWithTransitions = {
  name: 'router-view-transitions',
  template: `
    <transition :name="transitionName" mode="out-in">
      <component :is="currentView" :key="currentPath"></component>
    </transition>
  `,
  data() {
    return {
      currentView: null,
      currentPath: window.location.pathname,
      transitionName: 'fade',
      previousPath: null
    };
  },
  methods: {
    determineTransition(fromPath, toPath) {
      // Logique pour determiner le type de transition
      const fromIndex = routes.findIndex(r => r.path === fromPath);
      const toIndex = routes.findIndex(r => r.path === toPath);

      if (toIndex > fromIndex) {
        return 'slide-left'; // Navigation vers l'avant
      } else if (toIndex < fromIndex) {
        return 'slide-right'; // Navigation vers l'arriere
      }
      return 'fade';
    },

    navigateTo(path) {
      this.transitionName = this.determineTransition(this.currentPath, path);
      this.previousPath = this.currentPath;
      this.currentPath = path;
      this.updateView();
    }
  }
};

Mini-routeur complet et fonctionnel

Voici l’implementation complete d’un mini-routeur Vue.js :

// mini-router.js - Routeur Vue.js personnalise complet

class MiniRouter {
  constructor(options) {
    this.routes = options.routes || [];
    this.mode = options.mode || 'history'; // 'history' ou 'hash'
    this.currentPath = this.getCurrentPath();
    this.listeners = [];

    this.init();
  }

  getCurrentPath() {
    if (this.mode === 'hash') {
      return window.location.hash.slice(1) || '/';
    }
    return window.location.pathname;
  }

  init() {
    // Ecouter les changements d'historique
    window.addEventListener('popstate', () => {
      this.currentPath = this.getCurrentPath();
      this.notifyListeners();
    });

    // Intercepter les clics sur les liens
    document.addEventListener('click', (e) => {
      const link = e.target.closest('a[href]');
      if (link && this.isInternalLink(link)) {
        e.preventDefault();
        this.push(link.getAttribute('href'));
      }
    });
  }

  isInternalLink(link) {
    const href = link.getAttribute('href');
    return href && href.startsWith('/') && !link.hasAttribute('target');
  }

  push(path) {
    if (path === this.currentPath) return;

    if (this.mode === 'hash') {
      window.location.hash = path;
    } else {
      window.history.pushState({ path }, '', path);
    }

    this.currentPath = path;
    this.notifyListeners();
  }

  replace(path) {
    if (this.mode === 'hash') {
      window.location.replace(`#${path}`);
    } else {
      window.history.replaceState({ path }, '', path);
    }

    this.currentPath = path;
    this.notifyListeners();
  }

  back() {
    window.history.back();
  }

  forward() {
    window.history.forward();
  }

  getRoute(path = this.currentPath) {
    // Gestion des routes avec parametres
    for (const route of this.routes) {
      const match = this.matchRoute(route.path, path);
      if (match) {
        return { ...route, params: match.params };
      }
    }
    return null;
  }

  matchRoute(routePath, actualPath) {
    // Support des parametres dynamiques (:id, :slug, etc.)
    const routeParts = routePath.split('/');
    const actualParts = actualPath.split('/');

    if (routeParts.length !== actualParts.length) return null;

    const params = {};

    for (let i = 0; i < routeParts.length; i++) {
      if (routeParts[i].startsWith(':')) {
        // Parametre dynamique
        params[routeParts[i].slice(1)] = actualParts[i];
      } else if (routeParts[i] !== actualParts[i]) {
        return null;
      }
    }

    return { params };
  }

  onRouteChange(callback) {
    this.listeners.push(callback);
    return () => {
      this.listeners = this.listeners.filter(l => l !== callback);
    };
  }

  notifyListeners() {
    const route = this.getRoute();
    this.listeners.forEach(callback => callback(route, this.currentPath));
  }
}

// Plugin Vue pour le routeur
const RouterPlugin = {
  install(app, options) {
    const router = new MiniRouter(options);

    // Rendre le routeur accessible globalement
    app.config.globalProperties.$router = router;

    // Composant router-view
    app.component('RouterView', {
      template: `
        <transition :name="transition" mode="out-in">
          <component :is="currentComponent" :key="currentPath"></component>
        </transition>
      `,
      data() {
        return {
          currentComponent: null,
          currentPath: router.currentPath,
          transition: 'fade'
        };
      },
      created() {
        this.updateComponent();
        router.onRouteChange(() => {
          this.currentPath = router.currentPath;
          this.updateComponent();
        });
      },
      methods: {
        updateComponent() {
          const route = router.getRoute();
          if (route) {
            this.currentComponent = route.component;
          } else {
            this.currentComponent = options.notFoundComponent || {
              template: '<div><h1>404 - Page non trouvee</h1></div>'
            };
          }
        }
      }
    });

    // Composant router-link
    app.component('RouterLink', {
      props: ['to'],
      template: `
        <a :href="to" @click.prevent="navigate" :class="{ active: isActive }">
          <slot></slot>
        </a>
      `,
      computed: {
        isActive() {
          return router.currentPath === this.to;
        }
      },
      methods: {
        navigate() {
          router.push(this.to);
        }
      }
    });
  }
};

// Utilisation
const app = Vue.createApp({
  template: `
    <div id="app">
      <nav>
        <router-link to="/">Accueil</router-link>
        <router-link to="/about">A propos</router-link>
        <router-link to="/contact">Contact</router-link>
      </nav>
      <main>
        <router-view></router-view>
      </main>
    </div>
  `
});

app.use(RouterPlugin, {
  routes: routes,
  mode: 'history'
});

app.mount('#app');

Quand utiliser un routeur custom vs Vue Router ?

Tableau comparatif

CritereRouteur CustomVue Router
Taille du bundle~2-5 KB~25 KB
Courbe d’apprentissageFaibleMoyenne
FonctionnalitesBasiquesCompletes
Routes imbriqueesA implementerInclus
Guards de navigationA implementerInclus
Lazy loadingManuelAutomatique
Scroll behaviorManuelConfigurable
Support TypeScriptA ajouterExcellent
DevToolsNonOui
DocumentationA ecrireComplete
CommunauteAucuneLarge
MaintenanceVousEquipe Vue

Quand choisir un routeur personnalise ?

Utilisez un routeur custom si :

  1. Votre application a moins de 5-10 routes
  2. Vous n’avez pas besoin de routes imbriquees
  3. La taille du bundle est critique (PWA, mobile)
  4. Vous voulez apprendre les concepts fondamentaux
  5. Vous avez des besoins de routage tres specifiques

Utilisez Vue Router si :

  1. Votre application est moyenne a grande
  2. Vous avez besoin de routes imbriquees
  3. Vous voulez des guards de navigation
  4. Vous travaillez en equipe
  5. Vous avez besoin du support DevTools

Bonnes pratiques

1. Nommez toujours vos routes

const routes = [
  {
    path: '/utilisateur/:id',
    name: 'user-profile', // Facilite la navigation programmatique
    component: UserProfile
  }
];

// Navigation par nom
router.push({ name: 'user-profile', params: { id: 123 } });

2. Utilisez des meta-donnees pour la gestion d’etat

{
  path: '/admin',
  component: AdminDashboard,
  meta: {
    requiresAuth: true,
    roles: ['admin', 'super-admin'],
    title: 'Tableau de bord admin'
  }
}

3. Implementez des guards de navigation

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    next('/login');
  } else {
    next();
  }
});

4. Gerez proprement les erreurs de chargement

const AsyncComponent = () => ({
  component: import('./HeavyComponent.vue'),
  loading: LoadingComponent,
  error: ErrorComponent,
  delay: 200,
  timeout: 3000
});

5. Utilisez le lazy loading pour les routes volumineuses

const routes = [
  {
    path: '/dashboard',
    component: () => import('./views/Dashboard.vue')
  }
];

6. Preservez la position de scroll

const router = {
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition;
    }
    if (to.hash) {
      return { el: to.hash };
    }
    return { top: 0 };
  }
};

Pieges courants a eviter

1. Ne pas gerer l’evenement popstate

// MAUVAIS - Les boutons retour/avant ne fonctionnent pas
navigateTo(path) {
  window.history.pushState({}, '', path);
  this.updateView();
}

// BON - Ecouter popstate
window.addEventListener('popstate', () => {
  this.currentPath = window.location.pathname;
  this.updateView();
});

2. Oublier de nettoyer les ecouteurs d’evenements

// MAUVAIS - Fuite de memoire
created() {
  window.addEventListener('popstate', this.handlePopState);
}

// BON - Nettoyer a la destruction
beforeUnmount() {
  window.removeEventListener('popstate', this.handlePopState);
}

3. Ne pas gerer les routes inexistantes

// MAUVAIS - Erreur si route non trouvee
this.currentView = routes.find(r => r.path === path).component;

// BON - Verifier et fallback sur 404
const route = routes.find(r => r.path === path);
this.currentView = route ? route.component : NotFoundComponent;

4. Utiliser des chemins relatifs

// MAUVAIS - Problemes avec les routes imbriquees
navigateTo('profil'); // Resultat imprevisible

// BON - Toujours utiliser des chemins absolus
navigateTo('/utilisateur/123/profil');

5. Ignorer l’accessibilite

// MAUVAIS - Pas d'annonce pour les lecteurs d'ecran
updateView() {
  this.currentComponent = route.component;
}

// BON - Annoncer le changement de page
updateView() {
  this.currentComponent = route.component;

  // Mettre a jour le titre
  document.title = route.meta.title;

  // Annoncer pour les lecteurs d'ecran
  const announcement = document.getElementById('route-announcer');
  if (announcement) {
    announcement.textContent = `Navigation vers ${route.meta.title}`;
  }
}

Conclusion

La creation d’un systeme de routage personnalise avec Vue.js est un excellent exercice pour comprendre les mecanismes fondamentaux qui font fonctionner les SPA modernes. En maitrisant les concepts de :

  • Composants dynamiques avec :is
  • History API pour la gestion de l’historique
  • Event listeners pour intercepter la navigation
  • Gestion d’erreurs avec les pages 404
  • Transitions CSS pour une UX fluide

Vous serez mieux equipe pour :

  1. Debugger les problemes de routage dans vos applications
  2. Optimiser les performances en comprenant ce que fait Vue Router sous le capot
  3. Personnaliser le comportement de navigation selon vos besoins specifiques
  4. Evaluer quand un routeur complet est necessaire vs une solution legere

Pour les projets de production, Vue Router reste la solution recommandee grace a sa robustesse, sa documentation complete et son integration parfaite avec l’ecosysteme Vue. Cependant, pour des prototypes rapides, des applications tres simples ou des besoins educatifs, un routeur personnalise peut etre tout a fait adapte.

Le code presente dans cet article peut servir de base pour vos propres experimentations. N’hesitez pas a l’adapter et a l’enrichir selon vos besoins specifiques.


Ressources supplementaires

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles