f in x
Vue Router 4 — Advanced Routing with Lazy Loading and Navigation Guards for Performant Apps
> cd .. / HUB_EDITORIALE
Sviluppo di siti web

Vue Router 4 — Advanced Routing with Lazy Loading and Navigation Guards for Performant Apps

[2026-06-30] Author: Ing. Calogero Bono
Zenithby Meteora Web The operating system for your business. Social, clients, bookings and invoices in one platform. Gyms, barbers, professionals. Discover Zenith Free demo · no card

Your Vue app loads everything at once and users wait seconds to see the first screen. Every kilobyte that arrives immediately is a potential customer lost. We see it in projects that come to us for audits: static routes, 2MB bundles, users bouncing. The problem isn't Vue — it's how you handle the router. Vue Router 4 gives you full control over what loads and when, but most apps don't take advantage of it. This is not theory: we'll show you exactly how to implement lazy loading and navigation guards for a fast, secure, scalable app.

Why lazy loading components is essential for Vue apps?

When you define a route with component: MyComponent, all that component's code ends up in the initial bundle. If you have 50 routes, you load 50 components even if the user sees only one. The result: high load times, poor Core Web Vitals, user churn. Lazy loading solves this by splitting code into chunks loaded on demand. We, at Meteora Web, reduced the bundle size of an app with over 60 routes by 55% simply by switching to dynamic imports in routes.

How it works technically

Vue Router 4 natively supports the () => import('./views/Dashboard.vue') syntax. When the user navigates to that route, the browser downloads the corresponding chunk. No extra library, no plugin. It works with Vite (which uses native dynamic imports) and webpack (which generates separate chunks).

Sponsored Protocol

// Before: single bundle
const routes = [
  { path: '/dashboard', component: Dashboard }
];

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

Note: if you use webpack, add a magic comment for readable chunk names:

component: () => import(/* webpackChunkName: 'dashboard' */ './views/Dashboard.vue')

With Vite it's not needed, but you can use /* @vite-ignore */ if necessary.

How to implement lazy loading for routes in Vue Router 4?

Start with a typical router/index.js file. We recommend structuring routes in separate arrays per section (e.g., public, protected, admin) and applying lazy loading to every component.

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 }
  },
  // more routes...
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

export default router;

Every route is now lazy. No changes needed elsewhere. For nested routes, also apply lazy loading to the child route components.

Sponsored Protocol

Common mistake: forgetting to lazy load layout or 404 fallback components. We've seen apps where the home was lazy but the 404 wasn't, negating part of the gain.

What are navigation guards and how do they protect routes?

Navigation guards are functions that run before, during, or after a navigation. They let you control whether a user can access a route, redirect them, or show a loading state. Three levels exist: global (on every navigation), per route (defined in the route configuration), per component (inside the component using onBeforeRouteLeave).

Global beforeEach guard – the most common

We use it to check authentication. Suppose your Pinia store contains user. If the route has meta.requiresAuth: true, we check if the user is logged in; otherwise redirect to /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();
  }
});

Note: useAuthStore() can be called inside the guard because Pinia works within the Vue context. If you use a generic composable, ensure it's available or use a simple reactive variable.

Sponsored Protocol

Per route guard – more specific

You can define a guard directly in the route object, useful for route-specific logic.

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();
      }
    }
  }
];

Warning: per route guards do not trigger on child routes! For nested routes, replicate the guard or use a global one.

What strategies to use for navigation guards in a real app?

A common mistake is writing overly generic guards or duplicating logic. We adopt this architecture:

  • Single global guard: handles authentication and redirects. If the user is not logged in and the route is protected, redirect to /login preserving the original path.
  • Meta fields: use meta for flags like requiresAuth, role, guest (accessible only to unauthenticated users).
  • Async handling: if auth check requires an API call (e.g., refresh token), execute it here, not in the component.
  • For advanced components: use onBeforeRouteLeave to confirm exits (e.g., unsaved form).
// Example of global guard with async check
router.beforeEach(async (to, from, next) => {
  const authStore = useAuthStore();
  
  // If user not initialized and route needs auth, fetch user
  if (to.meta.requiresAuth && authStore.user === null) {
    try {
      await authStore.fetchUser();
    } catch (error) {
      // Token expired, 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();
  }
});

How to combine lazy loading and guards for optimal performance?

Lazy loading fetches the component code only when needed. Navigation guards run before the component is loaded. This means if the guard blocks access, the chunk is never requested. Result: bandwidth savings and better performance. We always enforce this combination: every protected route has meta.requiresAuth and the global guard decides the fate before triggering the import.

Sponsored Protocol

Practical example: Consider a /invoicing route that loads a heavy component with chart libraries. If the user is not logged in, the guard redirects immediately and the chunk is never downloaded. Less traffic, more speed.

Sponsored Protocol

// Protected and lazy route
{
  path: '/invoicing',
  component: () => import('@/views/Invoicing.vue'),
  meta: { requiresAuth: true }
}

// Global guard intercepts and if not auth, chunk is never requested.

Warning: don't use beforeEnter with lazy loading if the guard depends on the component itself (e.g., async data from the component). For that, use onBeforeRouteEnter inside the component.

What to do right now

  1. Scan all your routes: every static component: should become component: () => import(...).
  2. Add a global guard for authentication and, if needed, roles. Use meta for permissions.
  3. Test chunk names in production: open Network tab in DevTools and verify that each navigation loads only the required chunk.
  4. Verify guards work with proper redirects (save the original route to redirect back after login).
  5. Check coverage: routes you don't want lazy (e.g., critical landing page) can stay static, but everything else goes lazy.

For the full Vue 3 and Composition API approach, check our main guide: Vue.js 3 and Composition API — Reactive and Scalable Web Development.

Official documentation: Vue Router Lazy Loading.

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere informatico, fondatore di Meteora Web e Zenith OS. System administrator e progettista di piattaforme, app e CMS proprietari, con esperienza in sviluppo full-stack, marketing digitale ed ecosistema Google.
[ Read Full Dossier ]

> METEORA_WEB // DIGITAL AGENCY

We build the digital presence your business deserves.

Websites, social media, online advertising, e-commerce and high-performance hosting, engineered with method by computer engineers in Sciacca, for all of Italy.

> MW_JOURNAL

> READ_ALL()