La tua app Vue carica tutto in blocco e l'utente aspetta secondi prima di vedere la prima schermata. Ogni kilobyte che arriva subito è un potenziale cliente perso. Noi lo vediamo nei progetti che ci arrivano per audit: rotte statiche, bundle da 2MB, utenti che abbandonano. Il problema non è Vue, è come gestisci il router. Vue Router 4 dà il controllo totale su ciò che viene caricato e quando, ma la maggior parte delle app non lo sfrutta. Qui non parliamo di teoria: ti mostriamo esattamente come implementare lazy loading e navigation guards per avere un'app veloce, sicura e che scala.
Perché il lazy loading dei componenti è essenziale per le Vue app?
Quando definisci una route con component: MyComponent, tutto il codice di quel componente finisce nel bundle iniziale. Se hai 50 rotte, carichi 50 componenti anche se l'utente ne vede una sola. Il risultato: tempi di caricamento alti, Core Web Vitals sotto la media, utenti che se ne vanno. Il lazy loading risolve spezzando il codice in chunk caricati su richiesta. Noi, di Meteora Web, abbiamo ridotto bundle di app con oltre 60 rotte del 55% semplicemente passando a import dinamici nelle route.
Come funziona tecnicamente
Vue Router 4 supporta nativamente la sintassi () => import('./views/Dashboard.vue'). Quando l'utente naviga verso quella route, il browser scarica il chunk corrispondente. Nessuna libreria extra, nessun plugin. Funziona con Vite (che usa import dinamici nativi) e con webpack (che genera chunk separati).
Sponsored Protocol
// Prima: bundle unico
const routes = [
{ path: '/dashboard', component: Dashboard }
];
// Dopo: lazy loading
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
}
];
Attenzione: se usi webpack, aggiungi un commento magico per dare un nome leggibile al chunk:
component: () => import(/* webpackChunkName: 'dashboard' */ './views/Dashboard.vue')
Con Vite non serve, ma puoi usare i /* @vite-ignore */ se necessario.
Come implementare il lazy loading delle route in Vue Router 4?
Partiamo da un file router/index.js tipico. Noi consigliamo di strutturare le rotte in array separati per sezione (es. pubbliche, protette, admin) e applicare lazy loading a ogni componente.
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue')
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { requiresAuth: true }
},
// altre rotte...
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
Ogni rotta ora è lazy. Nessun cambiamento nel resto dell'app. Se hai rotte nidificate, applica lazy loading anche ai componenti delle rotte figlie.
Sponsored Protocol
Errore comune: dimenticare che anche il componente di layout o di fallback (404) deve essere lazy. Noi abbiamo visto app in cui la home era lazy ma il 404 no, vanificando parte del vantaggio.
Cosa sono le navigation guards e come proteggono le rotte?
Le navigation guards sono funzioni che si eseguono prima, durante o dopo una navigazione. Ti permettono di controllare se l'utente può accedere a una rotta, reindirizzarlo o mostrare un caricamento. Esistono tre livelli: globali (su ogni navigazione), per route (definite nella configurazione della rotta), per componente (nel componente stesso con onBeforeRouteLeave).
Guard globale beforeEach – il più comune
Lo usiamo per verificare l'autenticazione. Supponiamo che il tuo store Pinia contenga user. Se la rotta ha meta.requiresAuth: true, controlliamo se l'utente è loggato; altrimenti redirect a /login.
import { useAuthStore } from '@/stores/auth';
router.beforeEach((to, from, next) => {
const authStore = useAuthStore();
if (to.meta.requiresAuth && !authStore.user) {
next({ name: 'Login', query: { redirect: to.fullPath } });
} else {
next();
}
});
Nota: useAuthStore() deve essere chiamato dopo l'inizializzazione del router? No, funziona perché Pinia si attiva all'interno del contesto Vue. Ma se usi un composable generico, assicurati che sia disponibile o usa una semplice variabile reattiva.
Sponsored Protocol
Guard per route – più specifico
Puoi definire una guard direttamente nell'oggetto route, utile per logiche specifiche di una rotta.
const routes = [
{
path: '/admin',
component: () => import('@/views/Admin.vue'),
meta: { requiresAuth: true, role: 'admin' },
beforeEnter: (to, from, next) => {
const authStore = useAuthStore();
if (authStore.user?.role !== 'admin') {
next({ name: 'Forbidden' });
} else {
next();
}
}
}
];
Attenzione: la guard per route non si attiva per le rotte figlie! Se hai rotte nidificate, devi replicare la guard o usare quella globale.
Quali strategie usare per le navigation guards in un'app reale?
Un errore comune è scrivere guard troppo generiche o duplicare la logica. Noi adottiamo questa architettura:
- Guard globale unica: gestisce autenticazione e redirect. Se l'utente non è loggato e la rotta è protetta, reindirizza a /login salvando il percorso originale.
- Meta campi: usa
metaper flag comerequiresAuth,role,guest(accessibile solo a utenti non loggati). - Gestione asincrona: se il check di autenticazione richiede una chiamata API (es. refresh token), esegui qui e non nel componente.
- Per componenti avanzati: usa
onBeforeRouteLeaveper confermare uscite (es. form non salvato).
// Esempio di guard globale con check asincrono
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore();
// Se l'utente non è inizializzato e serve l'autenticazione, chiama API
if (to.meta.requiresAuth && authStore.user === null) {
try {
await authStore.fetchUser();
} catch (error) {
// Token scaduto, redirect
next({ name: 'Login' });
return;
}
}
if (to.meta.requiresAuth && !authStore.user) {
next({ name: 'Login' });
} else if (to.meta.guest && authStore.user) {
next({ name: 'Dashboard' });
} else {
next();
}
});
Come combinare lazy loading e guards per ottimizzare il caricamento?
Il lazy loading carica il codice del componente solo quando serve. Le navigation guards girano prima che il componente venga caricato. Questo significa che se la guard blocca l'accesso, il chunk non viene nemmeno richiesto. Risultato: risparmio di banda e performance migliori. Noi forziamo sempre questa combinazione: ogni rotta protetta ha meta.requiresAuth e la guard globale decide il destino prima di avviare l'import.
Sponsored Protocol
Esempio pratico: supponi una rotta /fatturazione che carica un componente pesante con librerie grafiche. Se l'utente non è loggato, la guard reindirizza subito e il chunk non viene scaricato. Meno traffico, più velocità generale.
Sponsored Protocol
// Route protetta e lazy
{
path: '/fatturazione',
component: () => import('@/views/Fatturazione.vue'),
meta: { requiresAuth: true }
}
// La guard globale intercetta e se non auth, non scarica il chunk.
Attenzione: non usare beforeEnter con lazy loading se la guard dipende dal componente stesso (es. dati asincroni del componente). Per quello usa onBeforeRouteEnter nel componente.
Cosa fare adesso
- Scansiona tutte le tue route: ogni
component:statico va convertito incomponent: () => import(...). - Aggiungi una guard globale per autenticazione e, se serve, per ruoli. Usa
metaper i permessi. - Testa i nomi dei chunk in produzione: apri la scheda Network del DevTools e verifica che ogni navigazione carichi solo il chunk necessario.
- Verifica che le guard funzionino con reindirizzamenti corretti (salva la rotta originale per ridirigere dopo login).
- Controlla la copertura: quelle rotte che non vuoi siano lazy (es. landing page critica) possono rimanere statiche, ma tutte le altre vanno lazy.
Se vuoi approfondire l'intero approccio a Vue 3 e Composition API, leggi la nostra guida principale: Vue.js 3 e Composition API — Sviluppo Web Reattivo e Scalabile.
Per la documentazione ufficiale: Vue Router Lazy Loading.