Hook — 06 : 11 a.m., Bogotá ↔ Playa Venao stand-up
Rain hammered my Medellín window while Diego in Panama shared his screen: users hit/checkout
directly, got a blank page, then bounced. The culprit was a lazy-loaded route that fetched cart data before verifying auth. We rewired the flow with a global navigation guard, tucked a role flag intoroute.meta
, and split the flow into nested child routes for steps 1-3. A single redirect later, the page loaded in 1 s on 3 G, and refunds dropped to zero—leaving just enough time for Diego to catch the morning waves. That cross-coast rescue frames today’s exploration of Vue Router’s essential features.
Why Routing Strategy Shapes User Experience
Routing isn’t just URL mapping—it’s security, performance, and cognitive flow. In Vue.js, guards keep private routes private, meta
drives breadcrumbs and SEO, and nested routes cut bundle size by lazy-loading exactly what each view needs. Master these tools now and you’ll avoid pagination spaghetti, auth leaks, and progress-bar purgatory—no matter if your team is reviewing code from Brazil or Dominican surf shacks.
Setting Up Vue Router 4
bashCopyEditnpm i vue-router@4
tsCopyEdit// router.ts
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{ path: '/', component: () => import('@/pages/Home.vue') },
];
export const router = createRouter({
history: createWebHistory(),
routes,
});
Mount it:
tsCopyEditcreateApp(App).use(router).mount('#app');
The SPA now pushes history entries without full reloads.
Route Meta: Tiny Object, Huge Power
meta
attaches arbitrary data to any record.
tsCopyEdit{
path: '/checkout',
component: () => import('@/pages/Checkout.vue'),
meta: { requiresAuth: true, step: 1, breadcrumbs: 'Checkout' },
}
Use cases:
- Navigation guards (
requiresAuth
) - Layout switching (
fullscreen: true
) - Page titles (
title: 'Pricing'
) - Analytics categories
Because meta
merges across nested routes, children inherit parent flags—handy for wizard flows.
Navigation Guards in Action
Global Guard for Auth
tsCopyEditrouter.beforeEach(async (to, _from) => {
if (to.meta.requiresAuth && !authStore.loggedIn) {
return { path: '/login', query: { redirect: to.fullPath } };
}
});
The function can return false
to cancel, void
to continue, or a redirect object.
Per-Route Guard
tsCopyEdit{
path: '/admin',
component: () => import('@/pages/admin/AdminLayout.vue'),
beforeEnter: async () => {
const ok = await api.verifyRole('admin');
return ok || { path: '/' };
},
}
Runs before the chunk loads—saving bytes if the user lacks permissions.
In-Component Guard
tsCopyEditexport default defineComponent({
async beforeRouteLeave(_to, _from) {
if (form.dirty && !confirm('Leave without saving?')) return false;
},
});
Great for unsaved forms; keep DOM access local.
Nested Routes for Wizards and Dashboards
Consider a three-step checkout:
tsCopyEdit{
path: '/checkout',
component: () => import('@/layouts/CheckoutShell.vue'),
meta: { requiresAuth: true },
children: [
{ path: '', name: 'Cart', component: () => import('@/pages/Cart.vue') },
{ path: 'shipping', component: () => import('@/pages/Shipping.vue') },
{ path: 'payment', component: () => import('@/pages/Payment.vue') },
],
}
<router-view/>
in CheckoutShell.vue
swaps steps without re-fetching layout CSS or shipping methods already loaded in the shell.
Hands-On Example: Scroll Behavior + Title
tsCopyEditexport const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, _from, saved) {
if (saved) return saved; // back/forward
if (to.hash) return { el: to.hash }; // anchor
return { top: 0 }; // default
},
});
router.afterEach(to => {
document.title = to.meta.title ?? 'My Shop';
});
One function centralizes UX polish without sprinkling watchers across components.
Remote-Work Insight Box
While sprinting from a Costa Rican jungle lodge, our on-call DevOps noticed Googlebot flagged duplicate content. We added meta.robots = 'noindex'
to staging routes, then a global afterEach that injects <meta name="robots">
tags dynamically. Overnight SEO alerts vanished, and the only downtime that week was a toucan perched on the Wi-Fi antenna.
Performance & Accessibility Checkpoints
- Chunk Splitting – each nested route’s component imported with
() => import()
yields a distinct file; Lighthouse’s JS execution dips. - Focus Management – call
nextTick(() => el.focus())
in per-route guards to preserve a11y after redirects. - CLS – use route-level async data to preload content and avoid layout shift after navigation.
- Prefetching –
<router-link to="/pricing" preload>
hints VitePress to warm the cache on idle.
Common Pitfalls & Fast Fixes
Issue | Root Cause | Remedy |
---|---|---|
Guard infinite redirect loop | Missing authStore check inside /login | Exclude route via !to.meta.requiresAuth |
Hash anchors mis-scroll | CSS scroll margin not set | scroll-margin-top equal to fixed header |
Redundant fetches on child nav | Data loaded in each step | Move fetch to parent beforeRouteEnter or Pinia store |
Stale meta after param change | Meta defined on parent only | Watch $route.params or use watchEffect(() => route.meta…) |
Call-Out Table
Router Feature | One-liner Purpose |
---|---|
meta | Attach arbitrary data to routes |
beforeEach | Global guard for auth, A/B routing |
beforeEnter | Per-route guard that blocks chunk download |
Nested children | Reuse shells, reduce bundle size |
scrollBehavior | Control scroll restoration |
CLI Commands Worth Memorizing
Command | Job |
---|---|
npx vitae build --report | Visualize router chunk split |
vue add router | Vue-CLI plugin with history mode |
vite-plugin-pages | File-system routing à-la Nuxt |
Wrap-Up
Vue Router’s guards, meta fields, and nested routes form a trifecta: secure paths, smarter UX, and lean bundles. Use meta for flags, global guards for auth, per-route guards for role gates, and nested routes for multi-step views. Ship these patterns once, and teammates across Latin America can focus on features—not redirect bugs—no matter the Wi-Fi.
Have a routing riddle or victory? Drop it below; I’ll answer between flights and late-night arepa sessions.