Hal Penting yang Harus Diperhatikan di Laravel + Inertia.js
Tutorial Laravel JavaScript #laravel #inertiajs #php #vue

Hal Penting yang Harus Diperhatikan di Laravel + Inertia.js

A
Abd. Asis
5 min read
Bagikan:

Stack Laravel + Inertia.js punya daya tarik yang jelas: kamu mendapat SPA dengan React atau Vue, tapi routing dan controller tetap di Laravel. Tidak ada REST API yang harus dibangun dan dijaga secara terpisah. Satu codebase, satu alur berpikir.

Tapi di balik kesederhanaannya, ada sejumlah detail yang kalau diabaikan bisa menimbulkan masalah — dari props yang membengkak, query N+1 yang tidak kelihatan, hingga flash message yang tidak muncul karena pola redirect yang salah. Artikel ini membahas hal-hal yang sering jadi sumber masalah di proyek Laravel + Inertia, beserta pola yang benar untuk menanganinya.

Kirim Hanya Data yang Dibutuhkan Halaman

Props di Inertia di-serialize menjadi JSON dan dikirim bersama setiap respons halaman. Mengirim model Eloquent secara penuh — dengan semua atributnya — adalah salah satu kebiasaan yang paling umum dan paling mudah diperbaiki.

Selalu batasi data yang dikirim ke frontend:

// app/Http/Controllers/TugasController.php

public function show(Tugas $tugas): \Inertia\Response
{
    return Inertia::render('Tugas/Show', [
        'tugas' => $tugas->only('id', 'judul', 'deskripsi', 'status', 'deadline'),
        'assignee' => $tugas->pengguna->only('id', 'nama', 'avatar_url'),
    ]);
}

Untuk koleksi, gunakan API Resource atau pilih field langsung di query:

'daftar_tugas' => TugasResource::collection(
    Tugas::select('id', 'judul', 'status', 'deadline')
        ->whereBelongsTo($proyek)
        ->latest()
        ->paginate(20)
),

Ini juga berlaku untuk relasi — jangan eager load relasi yang tidak ditampilkan di halaman tersebut.

Deferred Props untuk Data yang Tidak Krusial di Awal

Kalau ada data yang tidak dibutuhkan untuk render awal halaman — misalnya statistik, chart, atau daftar aktivitas — gunakan Inertia::defer(). Data ini akan dimuat secara terpisah setelah halaman pertama tampil.

// app/Http/Controllers/DashboardController.php

public function index(): \Inertia\Response
{
    return Inertia::render('Dashboard', [
        'ringkasan_proyek' => Proyek::aktif()->count(),
        'statistik'        => Inertia::defer(fn () => $this->hitungStatistikMingguan()),
        'aktivitas_terbaru' => Inertia::defer(fn () => Aktivitas::terbaru()->take(10)->get()),
    ]);
}

Di sisi Vue, gunakan komponen <Deferred> untuk menampilkan fallback selama data dimuat:

<!-- resources/js/Pages/Dashboard.vue -->
<template>
    <Deferred data="statistik">
        <template #fallback>
            <SkeletonChart />
        </template>
        <StatistikMingguan :data="statistik" />
    </Deferred>
</template>

Hasilnya: initial page load terasa lebih cepat karena browser tidak menunggu semua data selesai diproses di server.

Partial Reloads untuk Update Data Tanpa Full Navigation

Saat user melakukan filter atau sort, melakukan full page navigation terasa berlebihan — apalagi kalau ada bagian halaman yang tidak berubah. Gunakan partial reloads untuk hanya memperbarui props tertentu:

<!-- resources/js/Pages/Proyek/Index.vue -->
<script setup>
import { router } from '@inertiajs/vue3'

const props = defineProps(['proyek', 'filter'])

function ubahFilter(status) {
    router.get(
        route('proyek.index'),
        { status },
        { only: ['proyek'], preserveState: true }
    )
}
</script>

Opsi only: ['proyek'] memastikan hanya prop proyek yang di-refresh. preserveState: true menjaga scroll position dan state form agar tidak reset.

Prefetching juga tersedia di komponen <Link> via atribut prefetch. Tambahkan ini pada link navigasi utama untuk transisi yang terasa instan meski data baru dimuat dari server.

Redirect Setelah Mutasi — Selalu ke Route GET

Ini aturan yang terlihat sepele tapi sering dilanggar: setelah POST, PUT, atau DELETE, controller harus selalu redirect ke route GET, bukan langsung merender halaman baru.

// app/Http/Controllers/ProyekController.php

// Benar
public function store(StoreProyekRequest $request): \Illuminate\Http\RedirectResponse
{
    $proyek = Proyek::create($request->validated());

    return to_route('proyek.show', $proyek)
        ->with('sukses', 'Proyek berhasil dibuat.');
}

// Salah — jangan lakukan ini
public function store(StoreProyekRequest $request): \Inertia\Response
{
    $proyek = Proyek::create($request->validated());

    return Inertia::render('Proyek/Show', ['proyek' => $proyek]);
}

Jika controller merender langsung setelah POST, browser tidak akan menyimpan halaman di history dengan benar, dan tombol back bisa menyebabkan form di-submit ulang.

Shared Data: Apa yang Layak Masuk Middleware

HandleInertiaRequests adalah tempat yang tepat untuk data yang dibutuhkan di setiap halaman — data auth, flash message, dan konfigurasi global. Tapi jangan memasukkan semua hal ke sini.

// app/Http/Middleware/HandleInertiaRequests.php

public function share(Request $request): array
{
    return [
        ...parent::share($request),
        'auth' => [
            'pengguna' => $request->user()?->only('id', 'nama', 'email', 'peran'),
        ],
        'flash' => [
            'sukses' => session('sukses'),
            'galat'  => session('galat'),
        ],
        'app' => [
            'nama' => config('app.name'),
            'env'  => app()->environment(),
        ],
    ];
}

Yang tidak layak masuk ke shared data: data yang hanya dibutuhkan oleh satu halaman tertentu, atau data yang memerlukan query berat. Shared data di-evaluate di setiap request, termasuk partial reloads.

Hati-hati dengan data yang memerlukan query database di shared data — ini akan berjalan di setiap request Inertia, termasuk partial reload. Gunakan lazy evaluation dengan closure jika data tidak selalu dibutuhkan.

N+1 Query di Props Koleksi

Inertia tidak secara otomatis mendeteksi N+1. Masalah ini mudah tersembunyi karena serialisasi terjadi saat controller mengembalikan respons — bukan saat query dijalankan.

Selalu gunakan eager loading untuk relasi yang ditampilkan:

// Bermasalah: N+1 karena setiap tugas akan query pengguna secara terpisah
'tugas' => Tugas::all()->map(fn ($t) => [
    'judul'    => $t->judul,
    'assignee' => $t->pengguna->nama,
]),

// Benar: satu query tambahan untuk semua pengguna
'tugas' => Tugas::with('pengguna:id,nama')
    ->select('id', 'judul', 'pengguna_id')
    ->paginate(25)
    ->through(fn ($t) => [
        'judul'    => $t->judul,
        'assignee' => $t->pengguna->nama,
    ]),

Gunakan Laravel Debugbar atau Clockwork selama development untuk memantau jumlah query per request.

Testing Respons Inertia

Testing controller Inertia tidak berbeda jauh dari testing controller biasa, tapi ada helper assertInertia yang membuat assertion lebih ekspresif:

// tests/Feature/ProyekControllerTest.php

use Inertia\Testing\AssertableInertia as Assert;

public function test_halaman_daftar_proyek_menampilkan_data_yang_benar(): void
{
    $pengguna = Pengguna::factory()->create();
    Proyek::factory(3)->for($pengguna, 'pemilik')->create();

    $this->actingAs($pengguna)
        ->get(route('proyek.index'))
        ->assertInertia(fn (Assert $page) => $page
            ->component('Proyek/Index')
            ->has('proyek.data', 3)
            ->has('proyek.data.0', fn (Assert $item) => $item
                ->hasAll(['id', 'judul', 'status'])
                ->missing('pengguna_id')
                ->etc()
            )
        );
}

Gunakan missing() untuk memverifikasi bahwa field sensitif tidak ikut dikirim ke frontend — ini juga berfungsi sebagai security test ringan.

Error Handling di Production

Untuk menampilkan halaman error yang konsisten dengan layout aplikasi (bukan halaman default Laravel), tangani exception lewat bootstrap/app.php:

// bootstrap/app.php

use Inertia\Inertia;

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->respond(function (Response $response, Throwable $e, Request $request) {
        $kodeStatus = $response->getStatusCode();

        if (! app()->environment('local') && in_array($kodeStatus, [403, 404, 500, 503])) {
            return Inertia::render('Galat', ['status' => $kodeStatus])
                ->toResponse($request)
                ->setStatusCode($kodeStatus);
        }

        return $response;
    });
})

Di Inertia v3, cara yang direkomendasikan adalah menggunakan Inertia::handleExceptionsUsing() di service provider — seperti yang dibahas di artikel tentang Inertia.js v3 beta.

Asset Versioning untuk Deployment yang Aman

Saat deploy versi baru, browser yang masih menyimpan halaman lama perlu diberi tahu untuk memuat ulang. Inertia menangani ini lewat asset versioning — ketika versi berubah, Inertia otomatis memicu full reload.

Pastikan method version() di middleware menghasilkan nilai yang berubah setiap deploy:

// app/Http/Middleware/HandleInertiaRequests.php

public function version(Request $request): ?string
{
    return parent::version($request);
}

Method parent::version() secara default menggunakan hash file public/mix-manifest.json atau public/build/manifest.json (Vite). Selama Vite menghasilkan manifest baru di setiap build, ini sudah cukup tanpa konfigurasi tambahan.

Kesimpulan

Laravel + Inertia adalah kombinasi yang solid, tapi ia bekerja paling baik ketika kita mengikuti alur yang memang dirancang untuknya: redirect setelah mutasi, data yang dikontrol ketat di props, dan shared data yang ringan. Sebagian besar masalah yang muncul di proyek Inertia bukan karena limitasi framework — tapi karena pola dari aplikasi tradisional yang dibawa masuk tanpa penyesuaian. Memahami cara Inertia mengelola state dan navigasi adalah kunci untuk menjaga aplikasi tetap bersih dan performant seiring bertambahnya kompleksitas.

Referensi

  1. 1Inertia.js — Dokumentasi Resmi
  2. 2Laravel Docs — HTTP Responses & Redirects
  3. 3Laravel Debugbar — GitHub

Tentang Penulis

Abd. Asis

Abd. Asis

Software Developer dan Laravel Programmer dari Madura, Indonesia. Passionate tentang PHP, Laravel, dan teknologi web modern.

Komentar

Artikel Terkait

Artikel lain yang mungkin menarik untuk kamu