This episode covers Vue Router: defining routes, dynamic and nested routes with lazy loading, scroll behavior and programmatic navigation, navigation guards, and route meta for protecting pages based on authentication needs.

An application with a single page isn't enough. When you need a home page, a product detail page, and a profile page, you need routing — the way to map URLs to components. Vue Router is Vue's official router and the backbone of single-page application navigation.
Episode 9 covers it from the ground up: defining routes, dynamic and nested routes, lazy loading to keep the bundle lean, scroll behavior and programmatic navigation, navigation guards, and route meta for per-route authentication.
Install Vue Router, then define the route table:
npm install vue-router@4import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/", name: "home", component: HomeView },
{ path: "/tentang", name: "tentang", component: AboutView },
],
});
export default router;createRouter({ history, routes }) builds the router; createWebHistory() uses the HTML5 History API so URLs are clean without a hash. Register the router with the app using createApp(App).use(router).mount("#app").
Templates display the active route through RouterView, and navigation uses RouterLink:
<template>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/tentang">Tentang</RouterLink>
</nav>
<RouterView />
</template><RouterLink to="/"> renders as an anchor with client-side navigation and no reload, and <RouterView /> renders the component for the currently active route.
A detail page usually has a URL like /produk/42; its param is read through route.params.id after calling useRoute() in the component:
const routes = [
{ path: "/produk/:id", name: "detail", component: ProductDetail },
];When the param changes, trigger a refetch with watch.
Routes containing sub-pages are organized with children:
const routes = [
{
path: "/akun",
component: AkunLayout,
children: [
{ path: "", component: AkunHome },
{ path: "pengaturan", component: AkunSetting },
],
},
];children are rendered inside AkunLayout through <RouterView />. This fits pages with tabs or a sidebar that share the same layout.
Don't import all pages at once; use dynamic imports so each page is fetched separately:
const routes = [
{
path: "/produk/:id",
component: () => import("../views/ProductDetail.vue"),
},
];component: () => import(...) loads the page only when it's opened, splitting the code per route — the foundation of code splitting that we go deeper into in episode 14.
A guard runs before a navigation is completed. The most common example: checking authentication:
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isLogin()) {
next({ name: "login", query: { redirect: to.fullPath } });
} else {
next();
}
});to.meta.requiresAuth reads the meta of the destination route; if login is required, it redirects to the login page with a redirect back.
Navigation can also be triggered from code with useRouter(), for example router.push({ name: "detail", params: { id } }) after a button is clicked, or router.back() to mimic the browser's back button.
When moving between pages, scrollBehavior(to, from, savedPosition) in the router config returns the scroll position: use savedPosition when returning via history, or { top: 0 } for a fresh navigation.
To keep a component independent of useRoute, enable props mode: { path: "/produk/:id", component: ProductDetail, props: true }. With props: true, :id is passed directly as an id prop, keeping the component clean and easy to test.
Route meta can carry policy data, such as who is allowed to open a page:
const routes = [
{
path: "/admin",
component: AdminView,
meta: { requiresAuth: true, roles: ["admin"] },
},
];The guard then reads meta to enforce the policy: if to.meta.roles doesn't include the user's role, redirect to a forbidden page. to.meta.roles.includes(roleUser()) checks whether the user's role is in the allowed list — the basis of role-based access that we build out fully in episode 12.
Info
All route security logic on the client is only UI convenience. Real validation must still happen on the server — this applies to all subsequent security episodes.
Episode 9 took you from a single page to a multi-page application: defining routes with createRouter, dynamic and nested routes, per-route lazy loading, programmatic navigation, scroll behavior, route props, and meta and guards for per-route access policies.
Key takeaways:
RouterView renders the route; RouterLink navigates without reload.:id is read through route.params.children creates nested routes with a shared layout.() => import(...).beforeEach guard is great for authentication checks.In the next episode 10, we'll cover modern state management — Pinia as Vue's official store, defining stores with state, getters, and actions, Pinia plugins, and best practices for state normalization and modular stores in large-scale applications.