September 27, 2026

Inertia.js.

Cara kerja Inertia.js: routing & controller tetap di Laravel, halaman dirender oleh React/Vue/Svelte. Membahas props, shared data, form, partial reload, deferred props, dan perubahan di Inertia 3.

Inertia.js adalah β€œlem” antara backend klasik (Laravel) dan frontend modern (React , Vue, Svelte). Kamu tetap menulis route dan controller di Laravel seperti biasa, tapi alih-alih mengembalikan Blade view, controller mengembalikan komponen halaman JavaScript beserta props-nya. Hasilnya terasa seperti SPA tanpa perlu membangun REST API, state management untuk data server, atau router di sisi client.

Versi: Inertia 3 rilis stabil Maret 2026 (Vite plugin, tanpa Axios, optimistic update). Starter kit Laravel dan banyak project masih memakai Inertia 2 β€” konsep di catatan ini sama, perbedaan utama ditandai.


1. Cara Kerja

Kunjungan pertama:
Browser ──GET /posts──▢ Laravel ──▢ HTML lengkap + <div data-page='{"component":"posts/index","props":{...}}'>
                                     └─ JS mem-boot React dan merender posts/index

Navigasi berikutnya (klik <Link>):
Browser ──XHR GET /posts/1 (header X-Inertia: true)──▢ Laravel
        ◀── JSON { component: "posts/show", props: {...}, url, version }
        └─ Inertia menukar komponen halaman tanpa full reload
Tanggung jawabDikerjakan oleh
Routing, middleware, auth, validasi, query DBLaravel
Rendering UI, interaktivitas, transisi halamanReact/Vue/Svelte
Menyambungkan keduanya (XHR, history, error validasi)Inertia

Inertia bukan framework dan bukan pengganti API. Kalau kamu butuh API publik untuk mobile app, tetap buat API terpisah.


2. Instalasi (Laravel + React)

Cara termudah adalah memakai starter kit (laravel new β†’ pilih React), lihat Laravel + React . Instalasi manual:

Server side

composer require inertiajs/inertia-laravel
php artisan inertia:middleware
// bootstrap/app.php
use App\Http\Middleware\HandleInertiaRequests;

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        HandleInertiaRequests::class,
    ]);
})

Root template resources/views/app.blade.php:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    @viteReactRefresh
    @vite('resources/js/app.tsx')
    <x-inertia::head />
</head>
<body>
    <x-inertia::app />
</body>
</html>

(Di Inertia 2 dan sebelumnya memakai directive @inertiaHead dan @inertia β€” masih didukung.)

Client side (Inertia 3)

npm install react react-dom @vitejs/plugin-react @inertiajs/react @inertiajs/vite
// vite.config.js
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import react from '@vitejs/plugin-react'
import inertia from '@inertiajs/vite'

export default defineConfig({
  plugins: [
    laravel({ input: ['resources/js/app.tsx'], refresh: true }),
    inertia(),
    react(),
  ],
})
// resources/js/app.tsx
import { createInertiaApp } from '@inertiajs/react'

createInertiaApp()   // plugin otomatis mencari halaman di ./pages atau ./Pages

Di Inertia 2, kamu menulis sendiri callback resolve (biasanya dengan import.meta.glob('./pages/**/*.tsx')) dan setup. Cara ini masih didukung di v3 bila tidak memakai plugin Vite.


3. Render Halaman & Props

// routes/web.php
use App\Models\Post;
use Inertia\Inertia;

Route::get('/posts', function () {
    return Inertia::render('posts/index', [
        'posts' => Post::with('user:id,name')
            ->latest()
            ->paginate(10)
            ->through(fn ($p) => [
                'id' => $p->id,
                'title' => $p->title,
                'author' => $p->user->name,
            ]),
    ]);
})->name('posts.index');

// shortcut untuk halaman tanpa data
Route::inertia('/about', 'about');
// resources/js/pages/posts/index.tsx
import { Head, Link } from '@inertiajs/react'

type Post = { id: number; title: string; author: string }

export default function Index({ posts }: { posts: { data: Post[] } }) {
  return (
    <>
      <Head title="Posts" />
      {posts.data.map((post) => (
        <Link key={post.id} href={`/posts/${post.id}`} prefetch>
          {post.title} β€” {post.author}
        </Link>
      ))}
    </>
  )
}

Penting: semua props dikirim ke browser sebagai JSON dan bisa dilihat di page source. Jangan kirim model utuh yang berisi kolom sensitif; pilih field yang dibutuhkan saja (only(), through(), atau API Resource).


4. Shared Data

Data yang dibutuhkan di semua halaman (user login, nama aplikasi, notifikasi) didefinisikan di middleware HandleInertiaRequests:

public function share(Request $request): array
{
    return [
        ...parent::share($request),
        'appName' => config('app.name'),
        'auth' => [
            'user' => fn () => $request->user()?->only('id', 'name', 'email'),
        ],
    ];
}
import { usePage } from '@inertiajs/react'

export default function Navbar() {
  const { auth } = usePage<{ auth: { user: { name: string } | null } }>().props
  return <span>{auth.user ? `Halo, ${auth.user.name}` : 'Tamu'}</span>
}

Bungkus nilai dengan closure (fn () =>) agar hanya dievaluasi saat dibutuhkan. Untuk pesan sekali tampil (toast β€œBerhasil disimpan”), gunakan flash data, bukan shared data permanen.


5. Form

Inertia punya dua cara: komponen <Form> dan hook useForm. Keduanya menangani error validasi Laravel secara otomatis β€” ketika validasi gagal, Laravel redirect back dan error muncul di errors.

Komponen <Form>

import { Form } from '@inertiajs/react'

export default function Create() {
  return (
    <Form action="/posts" method="post" resetOnSuccess>
      {({ errors, processing }) => (
        <>
          <input name="title" />
          {errors.title && <p className="text-red-600">{errors.title}</p>}
          <textarea name="body" />
          <button disabled={processing}>
            {processing ? 'Menyimpan...' : 'Simpan'}
          </button>
        </>
      )}
    </Form>
  )
}

Hook useForm

import { useForm } from '@inertiajs/react'

export default function Create() {
  const { data, setData, post, processing, errors, reset } = useForm({
    title: '',
    body: '',
  })

  function submit(e: React.FormEvent) {
    e.preventDefault()
    post('/posts', { onSuccess: () => reset() })
  }

  return (
    <form onSubmit={submit}>
      <input value={data.title} onChange={(e) => setData('title', e.target.value)} />
      {errors.title && <p>{errors.title}</p>}
      <button disabled={processing}>Simpan</button>
    </form>
  )
}

Di controller, cukup validasi dan redirect seperti aplikasi Blade biasa:

public function store(Request $request)
{
    $validated = $request->validate([
        'title' => ['required', 'max:255'],
        'body'  => ['required'],
    ]);

    $request->user()->posts()->create($validated);

    return to_route('posts.index');
}

6. Partial Reload & Deferred Props

Partial reload meminta ulang sebagian props saja β€” berguna untuk filter, polling, atau tombol refresh.

import { router, Link } from '@inertiajs/react'

router.reload({ only: ['posts'] })

<Link href="/posts?status=draft" only={['posts']}>Draft</Link>

Di server, props yang mahal sebaiknya dibungkus supaya tidak dihitung kalau tidak diminta:

return Inertia::render('dashboard', [
    'stats'   => fn () => $this->stats(),                   // dievaluasi hanya jika diminta
    'users'   => Inertia::optional(fn () => User::all()),   // HANYA saat diminta lewat `only`
    'notice'  => Inertia::always(fn () => $this->notice()), // selalu ikut, termasuk partial reload
    'reports' => Inertia::defer(fn () => Report::heavy()),  // dimuat setelah halaman tampil
]);
import { Deferred } from '@inertiajs/react'

<Deferred data="reports" fallback={<div>Memuat laporan...</div>}>
  <ReportTable />
</Deferred>

(Inertia::lazy() dari versi lama sudah digantikan Inertia::optional().)


7. Hal Baru di Inertia 3 (Ringkas)

FiturKeterangan
Vite plugin @inertiajs/viteResolusi halaman & SSR otomatis, createInertiaApp() jadi sangat ringkas
Tanpa AxiosMemakai HTTP client bawaan; Axios tetap bisa lewat adapter
useHttpRequest HTTP biasa (bukan navigasi halaman) dengan state seperti useForm
Optimistic updatesUI diperbarui dulu sebelum respons server, otomatis di-rollback jika gagal
Layout propsMengirim data ke persistent layout

Syarat adapter Laravel v3: minimal Laravel 11. Saat upgrade dari v2, baca upgrade guide resmi karena ada perubahan konfigurasi.


8. Pitfalls

  • Membocorkan data lewat props (password hash, token, kolom internal). Selalu pilih field.
  • N+1 query di props β€” Inertia tidak menyembunyikan masalah Eloquent ; pakai with().
  • Pakai <a href> biasa β†’ full page reload. Gunakan <Link>.
  • Redirect ke domain luar / halaman non-Inertia dari request Inertia β†’ gunakan Inertia::location($url).
  • Asset berubah setelah deploy β†’ Inertia mendeteksi perbedaan version() dan melakukan full reload; pastikan build Vite dijalankan saat deploy.
  • Butuh SEO untuk halaman publik β†’ aktifkan SSR (npm run build:ssr) atau render halaman tersebut dengan Blade.
  • Butuh klien selain web (mobile app, integrasi pihak ketiga) β†’ Inertia saja tidak cukup, buat API.

Pengalaman nyata: Watch Store dibangun dengan Laravel + Inertia + React, sementara Kos Management App justru dimigrasi dari monolith Inertia ke REST API + SPA terpisah karena kebutuhan multi-tenant dan frontend yang lebih independen.

Referensi: Inertia.js docs v3 , Forms , Partial reloads , Shared data .

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.

Comments