Fitur deferred props pada Inertia v2 (Inertia::defer()) memungkinkan server menunda kalkulasi data lambat hingga initial page payload selesai terkirim ke klien. Namun, saat digabungkan dengan Server-Side Rendering (SSR), deferred props kerap memicu dua masalah utama: hydration mismatch pada konsol browser dan skeleton flash yang merusak metrik Cumulative Layout Shift (CLS).
Masalah ini berakar dari ketidaksinkronan pohon DOM antara server markup awal dengan hasil rehidrasi client-side, terutama ketika komponen fallback mengembalikan struktur node yang tidak identik secara semantik.
Akar Masalah Hydration Mismatch pada Deferred Props
Saat request SSR diproses di backend (misal: Laravel), closure di dalam Inertia::defer() sengaja diabaikan. Node SSR process merender komponen dengan prop bernilai undefined. Komponen frontend membungkus area tersebut dengan komponen <Deferred> dan menampilkan slot #fallback.
Hydration mismatch terjadi ketika:
- Struktur Tag Berbeda: Fallback merender tag pembungkus yang berbeda dengan komponen aktual, misalnya
<div>untuk skeleton sementara komponen riil merender<section>atau<table>. - Whitespace dan Text Node Mismatch: Server merender fallback kosong atau placeholder string, sementara klien langsung menerima partial update sebelum fase hidrasi selesai, menyebabkan engine virtual DOM (Vue atau React) gagal mencocokkan DOM tree.
- Client-Only State Injection: Logika render fallback menggunakan kondisi dinamis browser seperti pengecekan
windowatau timestamp lokal yang menghasilkan atribut berbeda antara SSR dan klien.
Strategi Mengatasi Skeleton Flash dan Degradasi CLS
Skeleton flash terjadi saat deferred prop selesai di-fetch dalam waktu sangat singkat (di bawah 100ms). Komponen fallback muncul sekejap lalu digantikan oleh data aktual, memicu flicker visual yang agresif. Jika dimensi skeleton tidak seimbang dengan konten final, browser terpaksa menghitung ulang geometri halaman (layout shift).
1. Isolasi Dimensi dengan CSS Aspect-Ratio dan Containment
Jangan biarkan skeleton menentukan ukuran wadah secara elastis tanpa batas. Bungkus komponen deferred dengan container yang memiliki reservasi tinggi minimum atau aspect-ratio tetap menggunakan properti CSS modern.
/* Mencegah pergeseran tata letak saat skeleton berganti ke konten aktual */
.deferred-card-wrapper {
contain-intrinsic-size: 0 320px;
content-visibility: auto;
min-height: 320px;
}2. Penyelarasan Semantik Komponen Fallback
Fallback skeleton harus merefleksikan hierarki node DOM target secara persis. Jika target adalah elemen list <ul> > <li>, fallback tidak boleh menggunakan kombinasi <div> > <span>.
Implementasi Kode: Backend Laravel dan Frontend Vue 3
Berikut pola konfigurasi backend menggunakan controller Laravel dan konsumsi data di Vue 3 dengan komponen pembungkus resmi Inertia v2.
Backend: Defer Prop di Controller
namespace App\Http\Controllers;
use App\Models\Metric;
use Inertia\Inertia;
use Inertia\Response;
class DashboardController extends Controller
{
public function __invoke(): Response
{
return Inertia::render('Dashboard/Index', [
'fastStats' => [
'activeUsers' => 1250,
],
// Kalkulasi berat ditunda dari initial payload SSR
'revenueMetrics' => Inertia::defer(fn () => Metric::calculateQuarterlyRevenue()),
]);
}
}Frontend: Penataan Boundary <Deferred>
Gunakan helper <Deferred> untuk mendefinisikan fallback yang sinkron antara SSR dan hidrasi klien.
<script setup>
import { Deferred } from '@inertiajs/vue3';
import MetricCard from './MetricCard.vue';
import MetricSkeleton from './MetricSkeleton.vue';
defineProps({
fastStats: Object,
revenueMetrics: Object,
});
</script>
<template>
<div class="dashboard-grid">
<div class="stat-box">
<p>Active Users: {{ fastStats.activeUsers }}</p>
</div>
<!-- Wrapper menahan dimensi struktural -->
<div class="deferred-card-wrapper">
<Deferred data="revenueMetrics">
<template #fallback>
<!-- Skeleton memiliki tag root <div> dan kelas identik dengan target -->
<MetricSkeleton />
</template>
<MetricCard :data="revenueMetrics" />
</Deferred>
</div>
</div>
</template>Optimasi Lanjutan dengan WhenVisible
Untuk komponen di bawah lipatan layar (below-the-fold), jangan gunakan deferred prop standar yang langsung dieksekusi setelah halaman termuat. Gunakan komponen WhenVisible milik Inertia v2 untuk menghemat bandwidth dan thread JavaScript.
<script setup>
import { WhenVisible } from '@inertiajs/vue3';
import ComplexChart from './ComplexChart.vue';
import ChartSkeleton from './ChartSkeleton.vue';
</script>
<template>
<WhenVisible data="chartData" :buffer="200">
<template #fallback>
<ChartSkeleton />
</template>
<ComplexChart :data="chartData" />
</WhenVisible>
</template>Atribut buffer="200" memulai prefetching data 200 piksel sebelum elemen masuk ke viewport, meniadakan skeleton flashing tanpa membebani initial hydration.
Automated Testing: Validasi Hidrasi Tanpa Peringatan Konsol
Mendeteksi hydration mismatch secara manual rentan terlewat. Gunakan Playwright untuk memverifikasi bahwa hidrasi SSR berjalan bersih tanpa memunculkan error atau warning di browser console.
import { test, expect } from '@playwright/test';
test('SSR hydration selesai tanpa warning console', async ({ page }) => {
const consoleMessages: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error' || msg.type() === 'warning') {
consoleMessages.push(msg.text());
}
});
await page.goto('/dashboard');
// Pastikan fallback render terlebih dahulu lalu data aktual masuk
await page.waitForSelector('.deferred-card-wrapper');
await page.waitForResponse(resp => resp.url().includes('revenueMetrics'));
// Evaluasi log console untuk mendeteksi error hidrasi Vue/React
const hydrationErrors = consoleMessages.filter((msg) =>
msg.toLowerCase().includes('hydration') ||
msg.toLowerCase().includes('mismatch')
);
expect(hydrationErrors).toEqual([]);
});Ringkasan Tindakan
- Pastikan root elemen komponen fallback dan komponen final menggunakan tag HTML, display CSS, dan padding yang sama.
- Kunci ruang tata letak menggunakan CSS
min-heightatauaspect-ratiountuk meredam CLS akibat penggantian skeleton. - Gunakan
WhenVisibledengan parameterbufferuntuk prop yang tidak esensial di viewport awal. - Jalankan end-to-end integration test via CI pipeline untuk memantau pesan log hidrasi secara otomatis.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!