Di Belajar Laravel #26 saya sudah mencoba jalur “server yang mengurus semuanya” lewat Livewire. Episode ini saya lanjut ke jalur kedua yang sering disebut sebagai lawannya: Inertia.js. Konsepnya beda arah — saya tetap menulis controller dan route seperti biasa, tapi tampilannya berupa komponen Vue yang sungguhan, lengkap dengan state lokal dan reaktivitas. Catatan ini saya susun sambil memasang Inertia di proyek uji coba, jadi sebagian besar isinya pengalaman pertama saya sendiri.
Inertia bukan API, bukan SPA murni
Yang paling mengubah cara pikir saya: Inertia tidak meminta saya membuat endpoint API sama sekali. Tidak ada routes/api.php khusus, tidak ada fetch manual, tidak ada client-side router. Di dokumentasinya Inertia menyebut pendekatan ini “modern monolith” — monolitik klasik yang server-driven, tapi tampilannya dirender di browser.
Caranya, controller mengembalikan nama komponen Vue plus data props, bukan HTML maupun JSON murni:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class PostController extends Controller
{
public function index(Request $request): Response
{
return Inertia::render('Posts/Index', [
'posts' => Post::query()
->when($request->string('q')->toString(), function ($q, $search) {
$q->where('title', 'like', "%{$search}%");
})
->paginate(10)
->through(fn (Post $post) => [
'id' => $post->id,
'title' => $post->title,
]),
]);
}
}Pada request pertama, server merender shell halaman biasa. Setelah itu setiap navigasi dikirim lewat XHR dengan header X-Inertia, dan server membalas dengan objek JSON berisi nama komponen + props. Vue lalu merender komponen itu di client. Inilah yang bikin saya merasa nyaman: semacam SPA, tapi route dan auth tetap di tangan Laravel yang sudah saya kuasai dari musim 1.
Page component: Vue yang benar-benar Vue
Di sisi klien, file halaman berupa SFC Vue biasa. Ini potongan resources/js/Pages/Posts/Index.vue:
<script setup>
import { ref, watch } from 'vue'
import { router, Link } from '@inertiajs/vue3'
import debounce from 'lodash/debounce'
const props = defineProps({ posts: Object, filters: Object })
const search = ref(props.filters.q ?? '')
watch(search, debounce((value) => {
router.get('/posts', { q: value }, {
preserveState: true,
replace: true,
})
}, 300))
</script>
<template>
<input v-model="search" placeholder="Cari postingan..." />
<table>
<tr v-for="post in posts.data" :key="post.id">
<td>{{ post.title }}</td>
</tr>
</table>
<Link :href="posts.next_page_url" v-if="posts.next_page_url">
Berikutnya
</Link>
</template>Dua hal yang saya catat. Pertama, router.get() di Inertia mengirim request ke route Laravel biasa — bukan ke API. Kedua, opsi preserveState: true membuat input pencarian tidak kosong setelah hasil diperbarui; tanpa opsi ini state Vue dibuang dan kursor melompat ke awal. Ini jebakan pertama yang saya temui, dan setelah sadar, semua request partial saya kasih opsi itu.
Shared data dan progress bar
Kebutuhan saya berikutnya: nama user login harus tampil di layout tiap halaman. Menariknya manual di setiap controller jelas ribet. Inertia punya shared data lewat middleware HandleInertiaRequests:
class HandleInertiaRequests extends Middleware
{
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'appName' => config('app.name'),
// lazy: baru dievaluasi kalau diminta frontend
'auth.user' => fn () => $request->user()
? $request->user()->only('id', 'name', 'email')
: null,
'flash' => [
'message' => fn () => $request->session()->get('message'),
],
]);
}
}Hasilnya menyatu otomatis ke props tiap halaman; di Vue saya membacanya lewat usePage().props. Saya juga senang menemukan bahwa Inertia punya progress bar bawaan (pembungkus ringan di atas NProgress) — otomatis muncul tiap navigasi, jadi user tahu halaman sedang dimuat. Tanpa itu, klik link terasa “mati” karena memang tidak ada reload penuh.
Catatan versi
Saat saya menulis ini, Inertia.js v3 sudah rilis (26 Maret 2026) dan adapter Laravel-nya berada di v3.7.0 (November 2026), mendukung Laravel 11.35 ke atas termasuk Laravel 13. Yang berubah di v3: klien XHR dibangun sendiri oleh Inertia, ada plugin Vite resmi @inertiajs/vite, dan pemasangan client-side jauh lebih ringkas. Laravel juga menyediakan starter kit resmi dengan stack Inertia + Vue supaya saya tidak merakit dari nol. Karena v3 baru, dukungan perbaikan bug v2 sudah berakhir September 2026 — jadi mulai proyek baru sekarang berarti langsung ke v3.
Kapan pilih Inertia, kapan pilih Livewire
Ini pertanyaan yang paling sering saya tanyakan ke diri sendiri. Setelah memakai keduanya, kesimpulan sementara saya: pilih Livewire kalau mayoritas halaman adalah CRUD admin, form, dan widget interaktif, dan saya tidak ingin menyentuh JavaScript. Pilih Inertia + Vue kalau tampilannya butuh state lokal yang kompleks, animasi sisi klien, komponen yang mungkin dipakai ulang di luar Laravel (misal mobile app nanti), atau saya memang sudah nyaman menulis Vue. Livewire seperti meminta pegawai mengerjakan semuanya; Inertia seperti saya duduk di kursi desainer dengan server tetap mengirim bahan. Keduanya sah, yang penting jangan memakai keduanya sekaligus di satu proyek — itu yang paling saya takuti dari awal, dan untungnya keputusannya bisa dibuat sejak awal proyek.
Buat saya, langkah belajar paling efektif adalah membuat satu halaman daftar + pencarian + pagination seperti di atas, lalu satu halaman form create yang memakai router.post dengan validasi error yang dikirim balik ke props. Dua halaman itu sudah cukup memperlihatkan seluruh pola. Episode berikannya saya akan lanjut ke Filament — admin panel yang bisa jadi jawaban kalau ternyata yang saya butuhkan memang hanya panel CRUD.
Sumber
Sumber utama yang saya baca untuk catatan ini:
Komentar Terbaru