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 :
- Navigation sans rechargement : L’utilisateur peut naviguer entre differentes “pages” sans attendre de chargement
- Partage de liens : Chaque vue possede sa propre URL, permettant de partager des liens directs
- Historique de navigation : Les boutons Precedent/Suivant du navigateur fonctionnent correctement
- 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 :
| Version | Description | Taille |
|---|---|---|
| Runtime only | Ne peut pas compiler les templates a la volee | ~22KB |
| Runtime + Compiler | Peut 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
| Critere | Routeur Custom | Vue Router |
|---|---|---|
| Taille du bundle | ~2-5 KB | ~25 KB |
| Courbe d’apprentissage | Faible | Moyenne |
| Fonctionnalites | Basiques | Completes |
| Routes imbriquees | A implementer | Inclus |
| Guards de navigation | A implementer | Inclus |
| Lazy loading | Manuel | Automatique |
| Scroll behavior | Manuel | Configurable |
| Support TypeScript | A ajouter | Excellent |
| DevTools | Non | Oui |
| Documentation | A ecrire | Complete |
| Communaute | Aucune | Large |
| Maintenance | Vous | Equipe Vue |
Quand choisir un routeur personnalise ?
Utilisez un routeur custom si :
- Votre application a moins de 5-10 routes
- Vous n’avez pas besoin de routes imbriquees
- La taille du bundle est critique (PWA, mobile)
- Vous voulez apprendre les concepts fondamentaux
- Vous avez des besoins de routage tres specifiques
Utilisez Vue Router si :
- Votre application est moyenne a grande
- Vous avez besoin de routes imbriquees
- Vous voulez des guards de navigation
- Vous travaillez en equipe
- 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 :
- Debugger les problemes de routage dans vos applications
- Optimiser les performances en comprenant ce que fait Vue Router sous le capot
- Personnaliser le comportement de navigation selon vos besoins specifiques
- 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
In-Article Ad
Dev Mode
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
Comment passer des paramètres URL avec Vue Router et props
Maîtrisez les routes dynamiques avec Vue Router. Passez des paramètres URL, utilisez props: true et naviguez programmatiquement dans vos applications Vue.js.
Gestion de Formulaires avec Vuex : Mutations, Getters et Actions
Maîtrisez la gestion des formulaires avec Vuex. Créez des mutations pour chaque champ, des getters pour l'état et des actions pour les API.
Migrer vers la Composition API Vue 3 : Guide Etape par Etape
Migrez vos composants Vue vers la Composition API. useStore, ref, onMounted et lifecycle hooks expliques avec des exemples pratiques.