Vue Router Tutorial: Membuat Multi Page App dengan Vue JS - Eps 6

Recap dari Episode Sebelumnya

Di Episode 5 kemarin, kita udah sukses memecah todo list jadi beberapa komponen lebih kecil. TodoForm, TodoItem, TodoStats, semuanya terstruktur rapi dengan props dan events. Aplikasi jadi lebih maintainable dan reusable. Tapi aplikasi itu masih satu halaman aja—single page application. Semua komponen nongol di layar yang sama.

Nah, sekarang kita mau level up. Bayangkan punya website yang perlu beberapa halaman berbeda. Misalnya halaman Home, About, Todo App, dan Contact. Nggak mungkin tampilin semuanya jadi satu layar, kan? Di sini Vue Router jadi jawabannya. Ini adalah navigation system yang ngubah single component menjadi multiple pages dalam satu SPA (Single Page Application). Dengan Vue Router, kamu bisa bangun aplikasi yang proper kayak website normal, tapi tetap maintain kecepatan dan smoothness dari SPA. Keren, kan?

Apa itu Vue Router & SPA?

Sebelum diving ke Vue Router, kita perlu paham konsep dasarnya. Apa itu SPA dan kenapa Vue Router penting. Nggak bakal ribet, kok.

Single Page Application (SPA)

Website tradisional bekerja kayak gini: setiap klik link, browser request ke server, server balik HTML baru, halaman refresh penuh. Proses ini terulang setiap pindah halaman. Slow banget.

SPA ngerjain hal beda. Hanya satu HTML file yang di-load di awal. Pas kamu pindah halaman, JavaScript handle semuanya. Nggak ada request ke server. Component berubah, URL berubah, tapi halaman nggak di-refresh. Semuanya terjadi di browser. Jauh lebih cepet dan smooth.

Perbandingan:

Traditional: Klik link → Request server → Reload halaman → Loading screen → Klik link lagi → Repeat

SPA: Klik link → JavaScript render → No reload → Instant → Klik link lagi → Instant lagi

Perbedaan experience-nya signifikan. SPA terasa kayak aplikasi native, bukan website biasa.

Vue Router: Official Routing Library

Vue Router adalah library resmi Vue yang handle routing untuk SPA. Tugasnya simple:

Pertama, manage URL. Ketika kamu pindah halaman, Vue Router ubah URL di address bar. Tapi ini terjadi di client-side, bukan server-side. URL berubah jadi /about, tapi browser nggak request ke server.

Kedua, render component yang tepat. Setiap URL punya component yang corresponding. URL /about render About component. URL /products render Products component. Vue Router handle semua otomatis.

Ketiga, client-side routing. Routing terjadi di browser. Nggak ada network latency. Transisi antar halaman instant.

Kapan Kamu Butuh Vue Router?

Vue Router dibutuhkan ketika:

Aplikasi punya multiple halaman atau multiple view yang muncul exclusive (satu waktu hanya satu yang ditampilin). Misalnya dashboard dengan Profile, Settings, Analytics. Setiap bagian punya URL dan view sendiri. E-commerce dengan product listing, product detail, shopping cart, checkout. Setiap section punya halaman sendiri.

Singkatnya: kalau aplikasi punya lebih dari satu halaman utama yang nggak ditampilin bersamaan, pakai Vue Router. Ini membuat aplikasi terasa seperti website proper dengan navigation proper, tapi tetap cepet kayak SPA.

Router Setup

Sekarang kita masuk ke bagian praktis. Membuat router itu sebenarnya super simple. Kamu nggak perlu setup dari nol. Ketika kamu bikin project Vue dengan create-vue, Vue Router udah auto-generated untuk kamu. Jadi yang perlu kamu lakukan adalah memahami struktur-nya dan customize sesuai kebutuhan.

File router/index.js

File ini adalah jantung dari routing system kamu. Di sini kamu define semua routes aplikasi. Mari kita lihat struktur default yang auto-generated:

import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    }
  ]
})

export default router

Ini adalah boilerplate dasar. Terlihat sederhana, tapi perlu kamu pahami setiap bagian-nya biar nggak confused nanti.

Mengenal Key Parts

createRouter adalah function dari Vue Router yang bikin instance router baru. Ini adalah object yang akan manage semua routing logic di aplikasi kamu. Tanpa ini, routing nggak jalan.

createWebHistory() adalah mode history yang kamu gunakan. Ini mode yang bikin URL terlihat normal kayak website biasa (/home, /about). Ada mode lain kayak createMemoryHistory() atau createHashHistory() tapi untuk sekarang, createWebHistory adalah yang paling standard dan recommended.

routes adalah array yang berisi semua route configuration. Setiap route adalah object yang define halaman atau view. Semakin banyak halaman, semakin banyak object dalam array ini.

path adalah URL path yang di-match. Kalau user masuk ke /, maka route dengan path: '/' yang akan active. Path harus unique dan selalu mulai dengan forward slash /.

name adalah identifier unik untuk route tersebut. Ini optional, tapi highly recommended. Dengan name, kamu bisa reference route ini tanpa harus hardcode URL-nya. Lebih fleksibel kalau nanti perlu ubah URL.

component adalah Vue component yang akan di-render ketika route ini active. Misalnya ketika user masuk ke /, component Home yang ditampilkan. Component harus sudah di-import di atas file.

Struktur di Praktik

Struktur file yang recommended adalah:

src/
├── router/
│   └── index.js        ← Router configuration
├── views/
│   ├── Home.vue        ← Page component
│   ├── About.vue
│   └── Contact.vue
└── App.vue

router/index.js adalah tempat define semua routes. views/ adalah folder yang berisi page-level component. Penting untuk pisahkan component biasa di components/ dengan page-level component di views/. Ini bikin project lebih organized dan mudah di-maintain.

Jadi step pertama dalam router setup adalah paham file structure ini. Dari sini, kamu siap untuk tambah route baru ke router configuration. Mari kita move ke step berikutnya dan define lebih banyak routes.

Create Pages

Sekarang kita perlu bikin halaman-halaman yang akan di-route oleh Vue Router. Ini adalah component-component yang akan ditampilkan ketika user navigasi ke URL tertentu. Kita akan bikin empat halaman: Home, About, TodoPage, dan Contact. Semuanya di-simpan di folder src/views/.

Home.vue

Ini adalah halaman pertama yang muncul ketika user masuk ke aplikasi. Sederhana aja, cuma welcome message:

<template>
  <div class="home">
    <h1>Welcome to Vue.js</h1>
    <p>This is the home page</p>
  </div>
</template>

<script setup>
// Bisa tambah logic di sini kalau perlu
</script>

<style scoped>
.home {
  padding: 40px 20px;
  text-align: center;
}

.home h1 {
  color: #42b983;
  margin-bottom: 20px;
}
</style>

image.png

About.vue

Halaman About untuk info tentang aplikasi. Simple, tapi bisa kamu develop lebih lanjut sesuai kebutuhan:

<template>
  <div class="about">
    <h1>About Us</h1>
    <p>Learn more about this app</p>
    <p>Ini adalah aplikasi Vue.js dengan multiple pages menggunakan Vue Router.</p>
  </div>
</template>

<script setup>
// About page logic di sini
</script>

<style scoped>
.about {
  padding: 40px 20px;
  max-width: 800px;
  margin: 0 auto;
}

.about h1 {
  color: #42b983;
}

.about p {
  line-height: 1.6;
  color: #333;
}
</style>

image.png

TodoPage.vue

Halaman untuk Todo App. Ini adalah tempat kamu paste TodoApp component dari Episode 5. Ingat bahwa TodoPage adalah wrapper, jadi di dalamnya kamu import TodoApp atau langsung paste code-nya:

<script setup>
import { ref, computed } from 'vue'
import TodoForm from '@/components/TodoForm.vue'
import TodoList from '@/components/TodoList.vue'
import TodoStats from '@/components/TodoStats.vue'

const todos = ref([])
const filter = ref('all')
const inputField = ref(null)

const totalTodos = computed(() => todos.value.length)
const activeTodos = computed(() => todos.value.filter((t) => !t.completed).length)
const completedTodos = computed(() => totalTodos.value - activeTodos.value)

const filteredTodos = computed(() => {
  if (filter.value === 'active') {
    return todos.value.filter((t) => !t.completed)
  }
  if (filter.value === 'completed') {
    return todos.value.filter((t) => t.completed)
  }
  return todos.value
})

function addTodo(text) {
  todos.value.push({
    id: Date.now(),
    text,
    completed: false,
  })
}

function toggleTodo(id) {
  const todo = todos.value.find((t) => t.id === id)
  if (todo) todo.completed = !todo.completed
}

function deleteTodo(id) {
  todos.value = todos.value.filter((t) => t.id !== id)
}

function setFilter(newFilter) {
  filter.value = newFilter
}
</script>

<template>
  <div class="todo-container">
    <h1>Todo List</h1>

    <TodoForm @add-todo="addTodo" :input-field="inputField" />

    <TodoStats :total="totalTodos" :active="activeTodos" :completed="completedTodos" />

    <div class="filter-buttons">
      <button :class="{ active: filter === 'all' }" @click="setFilter('all')" class="btn-filter">
        All ({{ totalTodos }})
      </button>
      <button
        :class="{ active: filter === 'active' }"
        @click="setFilter('active')"
        class="btn-filter"
      >
        Active ({{ activeTodos }})
      </button>
      <button
        :class="{ active: filter === 'completed' }"
        @click="setFilter('completed')"
        class="btn-filter"
      >
        Completed ({{ completedTodos }})
      </button>
    </div>

    <div v-if="filteredTodos.length === 0" class="empty-state">
      Belum ada todo untuk kategori ini. Mulai tambahkan todo baru sekarang!
    </div>

    <TodoList v-else :todos="filteredTodos" @toggle="toggleTodo" @delete="deleteTodo" />
  </div>
</template>

<style scoped>
/* GLOBAL STYLES */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* CONTAINER & LAYOUT */
.todo-container {
  max-width: 700px;
  margin: 0 auto;
  padding: 30px 20px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background: #f5f7fa;
  min-height: 100vh;
}

.todo-container h1 {
  text-align: center;
  color: #333;
  margin-bottom: 30px;
  font-size: 28px;
}

/* FILTER BUTTONS */
.filter-buttons {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin: 25px 0;
  flex-wrap: wrap;
}

.btn-filter {
  padding: 10px 20px;
  border: 2px solid #e0e0e0;
  background-color: white;
  color: #333;
  cursor: pointer;
  border-radius: 8px;
  font-weight: 600;
  font-size: 14px;
  transition: all 0.3s ease;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}

.btn-filter:hover {
  border-color: #42b983;
  color: #42b983;
  transform: scale(1.05);
  box-shadow: 0 4px 12px rgba(66, 185, 131, 0.2);
}

.btn-filter.active {
  background-color: #42b983;
  color: white;
  border-color: #42b983;
  box-shadow: 0 4px 15px rgba(66, 185, 131, 0.4);
}

/* EMPTY STATE */
.empty-state {
  padding: 60px 20px;
  text-align: center;
  color: #999;
  font-size: 16px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}

/* RESPONSIVE DESIGN */
@media (max-width: 600px) {
  .todo-container {
    padding: 20px 15px;
    min-height: auto;
  }

  .todo-container h1 {
    font-size: 24px;
    margin-bottom: 20px;
  }

  .filter-buttons {
    justify-content: center;
  }

  .btn-filter {
    font-size: 12px;
    padding: 8px 16px;
  }
}
</style>

image.png

Kalau kamu import TodoApp dari Episode 5, pastikan file path-nya benar. Kalau nggak ada folder khusus, kamu bisa copy seluruh TodoApp component ke halaman ini.

Contact.vue

Halaman Contact dengan form sederhana. Ini baru skeleton dulu, nanti bisa kamu elaborate lebih kompleks:

<template>
  <div class="contact">
    <h1>Contact Us</h1>
    <form @submit.prevent="handleSubmit">
      <div class="form-group">
        <label for="name">Name:</label>
        <input v-model="form.name" type="text" id="name" placeholder="Your name" />
      </div>

      <div class="form-group">
        <label for="email">Email:</label>
        <input v-model="form.email" type="email" id="email" placeholder="Your email" />
      </div>

      <div class="form-group">
        <label for="message">Message:</label>
        <textarea v-model="form.message" id="message" placeholder="Your message" rows="5"></textarea>
      </div>

      <button type="submit" class="btn-submit">Send Message</button>
    </form>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const form = ref({
  name: '',
  email: '',
  message: ''
})

function handleSubmit() {
  console.log('Form submitted:', form.value)
  // Reset form
  form.value = { name: '', email: '', message: '' }
  alert('Thank you for your message!')
}
</script>

<style scoped>
.contact {
  padding: 40px 20px;
  max-width: 600px;
  margin: 0 auto;
}

.contact h1 {
  color: #42b983;
  margin-bottom: 30px;
}

.form-group {
  margin-bottom: 20px;
}

.form-group label {
  display: block;
  margin-bottom: 8px;
  font-weight: 600;
  color: #333;
}

.form-group input,
.form-group textarea {
  width: 100%;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
  font-family: Arial, sans-serif;
}

.form-group input:focus,
.form-group textarea:focus {
  outline: none;
  border-color: #42b983;
  box-shadow: 0 0 5px rgba(66, 185, 131, 0.3);
}

.btn-submit {
  background: #42b983;
  color: white;
  padding: 12px 30px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-weight: 600;
  width: 100%;
  transition: background 0.3s;
}

.btn-submit:hover {
  background: #35a372;
}
</style>

image.png

File Organization

Semuanya disimpan di src/views/ dengan struktur kayak gini:

src/
├── views/
│   ├── Home.vue
│   ├── About.vue
│   ├── TodoPage.vue
│   └── Contact.vue
└── router/
    └── index.js

Penting! Pastikan nama file exact sesuai dengan yang kita reference di router configuration nanti. Vue adalah case-sensitive, jadi Home.vue beda dengan home.vue. Gunakan PascalCase untuk nama file component agar consistent dengan Vue convention.

Sekarang kamu udah punya empat halaman siap di-route. Next step adalah define routes untuk setiap halaman di router/index.js. Mari kita lanjut ke step berikutnya.

Define Routes

Sekarang kita update router/index.js untuk menambahkan semua halaman yang sudah kita buat. Ini adalah tahap di mana kita menghubungkan halaman-halaman dengan URL-nya. Router bakal tahu kalau user masuk ke /about, maka render About.vue. Kalau ke /todos, render TodoPage.vue. Dan seterusnya.

Update router/index.js

Pertama, import semua component dari folder views/ di bagian atas file:

import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import TodoPage from '@/views/TodoPage.vue'
import Contact from '@/views/Contact.vue'

Kemudian, update array routes dengan semua route configuration:

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/about',
    name: 'about',
    component: About
  },
  {
    path: '/todos',
    name: 'todos',
    component: TodoPage
  },
  {
    path: '/contact',
    name: 'contact',
    component: Contact
  }
]

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

export default router

Perhatikan bahwa routes sekarang sudah punya empat object, satu untuk setiap halaman.

Memahami Route Structure

path adalah URL yang akan di-match. Ketika user navigasi ke URL ini, route ini akan menjadi active dan component-nya akan di-render. Path harus selalu dimulai dengan forward slash /. Ini adalah wajib.

name adalah identifier unik untuk route. Ini bukan persyaratan teknis, tapi sangat direkomendasikan. Dengan name, kamu bisa mereferensikan route ini di template atau programmatic navigation tanpa harus hardcode path-nya. Kalau nanti path berubah, kamu hanya perlu update di router/index.js, bukan di semua tempat yang mereferensikan route ini.

component adalah Vue component yang akan ditampilkan. Ini harus sudah di-import di atas file sebelum direferensikan di routes array. Kalau component belum di-import, Vue akan throw error.

Testing Routes

Sekarang kamu bisa test router-nya. Jalankan aplikasi dengan npm run dev terus coba akses berbagai URL di address bar:

  • localhost:5173/ - seharusnya menampilkan Home page
  • localhost:5173/about - seharusnya menampilkan About page
  • localhost:5173/todos - seharusnya menampilkan TodoPage
  • localhost:5173/contact - seharusnya menampilkan Contact page

Kalau semuanya berjalan dengan baik dan halaman berubah sesuai URL, berarti router kamu sudah bekerja! Namun halaman akan terlihat blank karena kita belum setup navigation component di App.vue. Mari kita lanjut ke tahap berikutnya untuk menambahkan navigation links yang proper.

Define Routes

Sekarang kita update router/index.js untuk menambahkan semua halaman yang sudah kita buat. Ini adalah tahap di mana kita menghubungkan halaman-halaman dengan URL-nya. Router bakal tahu kalau user masuk ke /about, maka render About.vue. Kalau ke /todos, render TodoPage.vue. Dan seterusnya.

Update router/index.js

Pertama, import semua component dari folder views/ di bagian atas file:

import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import TodoPage from '@/views/TodoPage.vue'
import Contact from '@/views/Contact.vue'

Kemudian, update array routes dengan semua route configuration:

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/about',
    name: 'about',
    component: About
  },
  {
    path: '/todos',
    name: 'todos',
    component: TodoPage
  },
  {
    path: '/contact',
    name: 'contact',
    component: Contact
  }
]

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

export default router

Perhatikan bahwa routes sekarang sudah punya empat object, satu untuk setiap halaman.

Memahami Route Structure

path adalah URL yang akan di-match. Ketika user navigasi ke URL ini, route ini akan menjadi active dan component-nya akan di-render. Path harus selalu dimulai dengan forward slash /. Ini adalah wajib.

name adalah identifier unik untuk route. Ini bukan persyaratan teknis, tapi sangat direkomendasikan. Dengan name, kamu bisa mereferensikan route ini di template atau programmatic navigation tanpa harus hardcode path-nya. Kalau nanti path berubah, kamu hanya perlu update di router/index.js, bukan di semua tempat yang mereferensikan route ini.

component adalah Vue component yang akan ditampilkan. Ini harus sudah di-import di atas file sebelum direferensikan di routes array. Kalau component belum di-import, Vue akan throw error.

Testing Routes

Sekarang kamu bisa test router-nya. Jalankan aplikasi dengan npm run dev terus coba akses berbagai URL di address bar:

  • localhost:5173/ - seharusnya menampilkan Home page
  • localhost:5173/about - seharusnya menampilkan About page
  • localhost:5173/todos - seharusnya menampilkan TodoPage
  • localhost:5173/contact - seharusnya menampilkan Contact page

Kalau semuanya berjalan dengan baik dan halaman berubah sesuai URL, berarti router kamu sudah bekerja! Namun halaman akan terlihat blank karena kita belum setup navigation component di App.vue. Mari kita lanjut ke tahap berikutnya untuk menambahkan navigation links yang proper.

Navigation dengan router-link

image.png

App.vue adalah root component aplikasi kita. Ini adalah tempat di mana navigation bar ditempatkan dan di mana halaman-halaman akan di-render. Kita perlu update App.vue dengan router-link untuk melakukan navigasi antar halaman.

Update App.vue

Update file App.vue kamu menjadi seperti ini:

<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link>
      <router-link to="/about">About</router-link>
      <router-link to="/todos">Todos</router-link>
      <router-link to="/contact">Contact</router-link>
    </nav>

    <main>
      <router-view />
    </main>
  </div>
</template>

<style scoped>
nav {
  display: flex;
  gap: 20px;
  padding: 20px;
  background: #f5f5f5;
  border-bottom: 2px solid #ddd;
}

nav a {
  text-decoration: none;
  color: #333;
  padding: 10px 15px;
  border-radius: 5px;
  transition: all 0.3s;
}

nav a:hover {
  background: #e0e0e0;
}

nav a.router-link-active {
  background: #42b983;
  color: white;
}

main {
  padding: 40px 20px;
  max-width: 1200px;
  margin: 0 auto;
}
</style>

Apa itu router-link?

router-link adalah komponen Vue Router yang menggantikan tag <a> biasa. Ketika user klik router-link, halaman tidak akan di-refresh. Vue Router hanya menukar component yang ditampilkan. Attribute to menentukan URL tujuan, sama seperti href di tag <a> biasa.

Named Routes

Alih-alih melakukan hardcode path, kamu bisa menggunakan property name dari route:

<router-link :to="{ name: 'todos' }">
  Todos
</router-link>

Keuntungannya? Kalau path berubah dari /todos menjadi /todo-list, kamu hanya perlu update di router/index.js. Semua link tetap bekerja tanpa perlu mengubah di template.

router-view

router-view adalah placeholder tempat component di-render. Ketika user berada di home page, router-view merender Home. Di about page, merender About. Semuanya otomatis berdasarkan URL saat ini.

Active Link Styling

Vue Router secara otomatis menambahkan class router-link-active ke link yang sedang active. Kamu bisa menggunakan class ini untuk styling seperti di CSS di atas. Link yang active akan memiliki background hijau dan text putih. Ini memberikan visual feedback kepada user tentang halaman mana yang sedang dibuka.

Dynamic Routes

image.png

Sampai sekarang, semua route yang kita buat adalah static. Path-nya fixed dan predictable. Tapi bagaimana kalau kita membutuhkan satu component untuk ditampilkan di berbagai URL? Misalnya, component UserProfile harus bisa ditampilkan di /user/1, /user/2, /user/3, dan seterusnya. Di sini dynamic routes dengan parameter menjadi sangat membantu.

Route dengan Parameter

Dynamic routes menggunakan parameter di path. Parameter ditandai dengan colon : diikuti nama parameter:

{
  path: '/user/:id',
  name: 'user',
  component: UserProfile
}

Path /user/:id berarti aplikasi akan mencocokkan URL apa pun yang formatnya /user/sesuatu. Misalnya /user/1, /user/2, /user/john. Nilai dari parameter id bisa diakses di component menggunakan route.params.id.

Mengakses Parameter di Component

Di dalam component, kamu bisa mengakses parameter menggunakan composable useRoute:

<script setup>
import { useRoute } from 'vue-router'

const route = useRoute()
const userId = route.params.id
</script>

<template>
  <div class="user-profile">
    <h1>User ID: {{ userId }}</h1>
    <p>Menampilkan profile untuk user dengan ID {{ userId }}</p>
  </div>
</template>

useRoute() adalah composable yang memberikan akses ke informasi route saat ini. route.params adalah object yang berisi semua parameter dari URL. Jadi kalau URL adalah /user/42, maka route.params.id nilainya adalah 42.

Navigasi dengan Parameter

Untuk melakukan navigasi ke route dengan parameter, kamu gunakan object syntax dengan params:

<router-link :to="{ name: 'user', params: { id: 123 } }">
  User 123
</router-link>

Vue Router akan secara otomatis menyelesaikan ini menjadi /user/123. Kamu tidak perlu melakukan hardcode URL-nya. Ini membuat code lebih mudah dimaintain.

Use Cases Praktis

Dynamic routes sangat berguna di berbagai skenario. Aplikasi blog dengan halaman post terpisah - setiap post memiliki URL unik seperti /post/1, /post/2. Aplikasi e-commerce dengan product detail pages - setiap product memiliki URL /product/123, /product/456. User profile pages - setiap user memiliki halaman profile sendiri di /user/john, /user/jane.

Dengan dynamic routes, kamu tidak perlu membuat route terpisah untuk setiap user atau setiap product. Satu route dengan parameter cukup untuk menangani semuanya. Ini jauh lebih efficient dan scalable daripada membuat ratusan route secara manual.

Sekarang kamu memahami bagaimana membuat aplikasi yang lebih dynamic dan flexible dengan parameter routes. Selanjutnya, kita akan mengeksplorasi programmatic navigation - cara melakukan navigasi di aplikasi menggunakan JavaScript selain menggunakan declarative router-link.

Programmatic Navigation

Sampai sekarang kita gunakan router-link untuk navigasi, yang bersifat declarative. Tapi kadang kita perlu navigasi berdasarkan logika atau kondisi tertentu. Misalnya, setelah user submit form, redirect ke halaman success. Atau setelah login gagal, tampilkan error terus jangan pindah halaman. Di sini programmatic navigation dengan useRouter sangat berguna.

useRouter Composable

useRouter adalah composable yang memberikan akses ke router instance. Dengan ini, kamu bisa navigate secara programmatic menggunakan JavaScript:

import { useRouter } from 'vue-router'

const router = useRouter()

Setelah kamu punya instance router, kamu bisa panggil berbagai method untuk melakukan navigasi.

Navigate Methods

Ada beberapa method yang tersedia di router:

router.push() menambahkan entry baru ke history. Ini berarti user bisa tekan tombol back di browser dan kembali ke halaman sebelumnya. Kamu bisa pass string path atau object dengan nama route:

router.push('/about')
router.push({ name: 'todos' })
router.push({ path: '/user/123' })

router.replace() melakukan navigasi tapi tidak menambah entry ke history. Jadi user tidak bisa kembali ke halaman sebelumnya dengan tombol back. Ini berguna kalau halaman sebelumnya tidak relevan lagi, misalnya setelah login berhasil.

router.back() membawa user kembali satu halaman, equivalent dengan menekan tombol back di browser.

router.forward() membawa user maju satu halaman, sama dengan tombol forward di browser.

router.go() bisa bergerak maju atau mundur sesuai jumlah halaman. router.go(-1) sama dengan back satu halaman, router.go(2) maju dua halaman.

Use Cases Praktis

Programmatic navigation sangat berguna di berbagai situasi. Setelah user submit form, navigasi ke halaman success atau dashboard. Setelah user login berhasil, redirect ke dashboard atau home page. Setelah logout, kembali ke home page. Conditional navigation berdasarkan state atau permission user - misalnya kalau user nggak authenticated, redirect ke login page.

Contoh Praktis

Ini adalah contoh real dari programmatic navigation:

import { useRouter } from 'vue-router'

const router = useRouter()

function handleSubmit() {
  // Validasi dan simpan data
  if (formValid.value) {
    saveData()
    // Setelah data tersimpan, redirect ke success page
    router.push({ name: 'success' })
  } else {
    showErrorMessage('Form tidak valid!')
  }
}

Ketika user submit form, handleSubmit dipanggil. Kalau form valid, data disimpan dan user di-redirect ke success page menggunakan router.push(). Kalau tidak valid, tampilkan error message dan jangan navigasi.

Ini adalah power dari programmatic navigation. Kamu bisa control navigation flow berdasarkan business logic aplikasi kamu. Combine dengan conditional statements, kamu bisa membuat user experience yang sophisticated dan sesuai dengan kebutuhan aplikasi.

Sekarang kamu sudah tahu dua cara untuk navigate di Vue Router: declarative dengan router-link dan programmatic dengan router.push(). Use keduanya sesuai dengan kebutuhan - gunakan router-link untuk navigation yang simple, gunakan programmatic navigation kalau ada logic atau kondisi yang butuh dihandle.

404 Not Found

image.png

Apa yang terjadi kalau user masuk ke URL yang nggak ada di aplikasi? Misalnya /halaman-random atau /typo-url. Tanpa handling yang tepat, user akan lihat halaman kosong atau error yang nggak user-friendly. Untuk mengatasi ini, kita perlu catch-all route yang menangani semua URL yang nggak match dengan route yang sudah didefinisikan.

Catch-All Route

Tambahkan route ini di bagian paling akhir dari routes array di router/index.js:

{
  path: '/:pathMatch(.*)*',
  name: 'not-found',
  component: NotFound
}

Route ini menggunakan regex pattern /:pathMatch(.*)* yang akan match dengan URL apa pun yang belum di-handle oleh route sebelumnya. Penting banget! Route ini harus berada di paling akhir dari routes array. Kalau letaknya di awal atau tengah, route ini bakal match semua URL termasuk yang valid, dan route lainnya nggak pernah kena.

NotFound Component

Buat component baru bernama NotFound.vue di folder src/views/:

<template>
  <div class="not-found">
    <h1>404 - Page Not Found</h1>
    <p>Halaman yang kamu cari tidak ditemukan.</p>
    <p>Mungkin URL-nya salah atau halaman sudah dipindahkan.</p>
    <router-link to="/">Kembali ke Home</router-link>
  </div>
</template>

<script setup>
// Optional: bisa tambah logging atau tracking di sini
console.log('User mengakses halaman yang tidak ada')
</script>

<style scoped>
.not-found {
  padding: 60px 20px;
  text-align: center;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  color: white;
}

.not-found h1 {
  font-size: 72px;
  margin: 0 0 20px 0;
  font-weight: bold;
}

.not-found p {
  font-size: 18px;
  margin: 10px 0;
  line-height: 1.6;
}

.not-found a {
  display: inline-block;
  margin-top: 30px;
  padding: 12px 30px;
  background: white;
  color: #667eea;
  text-decoration: none;
  border-radius: 5px;
  font-weight: 600;
  transition: all 0.3s;
}

.not-found a:hover {
  transform: translateY(-3px);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}
</style>

Import di Router Configuration

Jangan lupa import NotFound component di router/index.js:

import NotFound from '@/views/NotFound.vue'

Setelah itu, tambahkan route catch-all di paling akhir routes array.

Testing 404 Page

Sekarang kamu bisa test dengan akses URL yang nggak ada, misalnya localhost:5173/random-page atau localhost:5173/tidak-ada. Kamu akan lihat NotFound component yang kamu buat.

Dengan catch-all route ini, aplikasi kamu jadi lebih professional dan user-friendly. Daripada user lihat halaman kosong atau error yang bingung, mereka lihat pesan yang jelas dan ada opsi untuk kembali ke home page.

Styling Navigation

image.png

Navigation bar adalah komponen yang paling sering dilihat user. Styling yang bagus bukan hanya membuat aplikasi terlihat professional, tapi juga meningkatkan user experience dan usability. Kita sudah punya navigation yang functional, sekarang kita improve dengan styling yang clean, modern, dan sesuai dengan brand Vue.js.

Navigation Styling yang Clean & Modern

Update bagian <style> di App.vue dengan styling yang menggunakan warna brand Vue.js:

<style scoped>
nav {
  display: flex;
  gap: 0;
  padding: 0;
  background: white;
  border-bottom: 1px solid #e5e7eb;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  position: sticky;
  top: 0;
  z-index: 100;
}

nav a {
  flex: 1;
  text-align: center;
  text-decoration: none;
  color: #1f2937;
  padding: 14px 20px;
  transition: all 0.2s ease;
  border-bottom: 2px solid transparent;
  position: relative;
  font-weight: 500;
  font-size: 15px;
}

nav a:hover {
  color: #42b983;
  border-bottom-color: #42b983;
}

nav a.router-link-active {
  color: #42b983;
  border-bottom-color: #42b983;
  font-weight: 600;
  background: rgba(66, 185, 131, 0.05);
}

main {
  padding: 40px 20px;
  max-width: 1200px;
  margin: 0 auto;
  min-height: calc(100vh - 70px);
}

/* Responsive design */
@media (max-width: 768px) {
  nav {
    flex-direction: column;
    gap: 0;
  }

  nav a {
    border-bottom: 2px solid transparent;
    border-left: 3px solid transparent;
    padding: 12px 16px;
    text-align: left;
  }

  nav a:hover {
    border-left-color: #42b983;
    border-bottom-color: transparent;
  }

  nav a.router-link-active {
    border-left-color: #42b983;
    border-bottom-color: transparent;
  }

  main {
    padding: 20px 15px;
    min-height: auto;
  }
}

/* Light gray hover state untuk better UX */
@media (hover: hover) {
  nav a {
    background: transparent;
  }

  nav a:hover {
    background: #f9fafb;
  }

  nav a.router-link-active {
    background: rgba(66, 185, 131, 0.08);
  }
}
</style>

Penjelasan Styling yang Clean & Modern

nav menggunakan background putih yang clean. Border bottom yang subtle memberikan separation yang elegan. Box-shadow yang minimal memberikan depth. position: sticky membuat nav tetap terlihat saat user scroll.

Warna #42b983 adalah official brand color Vue.js. Warna ini digunakan untuk hover dan active states, memberikan consistency dengan Vue ecosystem.

nav a menggunakan flex untuk equal width distribution. Border-bottom dengan transparent default membuat animated border effect yang smooth.

nav a:hover mengubah color menjadi Vue green dan menampilkan border bottom. Ini memberikan clear visual feedback yang intuitive.

nav a.router-link-active menunjukkan link yang active dengan color Vue green, border, dan subtle background. Background yang sangat light memberikan distinction tanpa terasa berlebihan.

Responsive design di mobile berubah vertical dan menggunakan left border alih-alih bottom border untuk space efficiency.

@media (hover: hover) adalah best practice modern untuk device yang support hover. Hover effects hanya muncul di device yang bisa hover, bukan di touch devices.

Hasilnya adalah navigation yang clean, modern, dan professional. Color scheme yang consistent dengan Vue memberikan sense of familiarity. User experience smooth dengan clear visual feedback di setiap interaction.

Kesimpulan

Selamat! Kamu sudah menyelesaikan Episode 6 tentang Vue Router. Dari memahami SPA sampai membuat aplikasi multi-page yang professional, kamu sudah mempelajari banyak hal penting tentang routing di Vue.

Recap yang Sudah Kita Pelajari

Vue Router adalah official routing library Vue untuk melakukan navigasi di Single Page Applications. Kamu sudah tahu dua cara navigasi: declarative dengan router-link, dan programmatic dengan router.push().

router-view adalah placeholder tempat page components di-render berdasarkan route yang active.

Dynamic routes dengan parameter membuat satu route menangani multiple halaman. Misalnya /user/:id bisa menangani /user/1, /user/2, /user/john.

Catch-all route dengan pattern /:pathMatch(.*)* menangani URL yang tidak terdaftar dan menampilkan 404 page. Route ini harus berada di paling akhir routes array.

Struktur Aplikasi yang Sudah Kita Buat

Kamu punya aplikasi dengan proper multi-page structure. App.vue adalah root component dengan navigation yang styled. Multiple pages di folder views/ di-route dengan clean. Navigation berfungsi smooth tanpa page reload, memberikan user experience yang superior.

Struktur routing sudah solid, design sudah clean. Ini adalah foundation yang kuat untuk aplikasi Vue yang lebih kompleks.

Selanjutnya

Episode 7 akan membahas Pinia, state management library untuk Vue. Kombinasi Vue Router dan Pinia adalah duo powerful untuk membangun aplikasi Vue yang professional dan production-ready.

Untuk master Vue.js lebih dalam, kunjungi BuildWithAngga.com dan explore comprehensive course serta komunitas developer yang supportive. Semangat belajar dan terus berkembang!