Skip to content

Membelah Grid: Halaman Pertama Server, Sisanya Klien

Adityo Guni Waluyo

Grid entitas pilar yang menahan render halaman terlalu lama. Halaman pertama tetap dirender server, sisanya pindah ke island klien.

Ringkasan

Halaman profil pilar lambat karena request data yang lambat. Menaikkan limit tidak mempercepat apa pun. Sekarang, delapan kartu pertama dikirim sebagai HTML dan sisanya sebagai JSON.

Halaman profil pilar di portal kota itu punya satu grid berjudul "Entitas Terbaru". Dua belas kartu, semuanya datang dari satu request server. Angka dua belas itu bukan keputusan desain, cuma nilai yang kebetulan dipilih waktu grid pertama kali dibuat, dan tidak ada yang pernah menghitung ulang. Suatu sore saya buka halaman itu di jaringan lambat, dan grid-nya baru muncul setelah seluruh respons API selesai. Tebakan pertama saya sederhana: naikkan saja limit-nya.

Tebakan itu salah arah. Yang membuat halaman terasa berat bukan jumlah HTML yang dikirim, melainkan kapan HTML itu boleh dikirim.

Menaikkan limit hanya memindahkan beban

Dokumentasi Next.js menyebutnya tanpa basa-basi: kalau ada request data yang lambat di Server Component, seluruh rute tertahan dirender sampai semua data selesai diambil [3]. Request fetch di sana juga tidak di-cache secara default dan menahan render sampai selesai [3]. Jadi limit 12 yang saya naikkan jadi 20 tidak mempercepat apa pun. Saya hanya memindahkan pekerjaan yang sama ke tempat yang lebih mahal, karena sekarang lebih banyak baris yang harus diambil sebelum satu byte pun sampai ke browser.

Ini bagian yang sering terlewat waktu orang membahas Server Component. Argumennya bukan "server lebih cepat", tapi "server menahan seluruh rute". Satu query lambat di grid membuat hero, judul, dan menu di halaman yang sama ikut menunggu.

Membelah daftar, bukan membungkusnya

Bentuk akhirnya bukan satu komponen klien yang membungkus seluruh grid. Halaman pilar tetap Server Component yang mengambil halaman pertama, sekarang dengan limit 8, di dalam satu blok Promise.all bersama kategori, hero, dan arus video. Hasilnya dikirim ke island klien sebagai baris awal, bersama total entitas, nama modul pilar, dan daftar kategori untuk deep-link kartu.

Island itu yang memegang sisanya: penghitung halaman, tombol "Muat lebih banyak", dan status pemuatan. Saat tombol ditekan, ia memanggil endpoint entitas yang sama dengan page berikutnya dan limit yang sama, lalu menambahkan hasilnya ke baris yang sudah tampil. Direktif 'use client' di file itu menandai satu titik masuk ke sisi klien, dan hanya file yang benar-benar dirender langsung dari Server Component yang perlu membawanya [2].

Kenapa tidak sekalian memindahkan seluruh grid ke klien? Karena halaman pertama tetap harus jadi HTML. Itu yang dibaca crawler, itu yang terlihat saat JavaScript belum jalan, dan itu yang menentukan First Contentful Paint. Batas server-klien di sini berjalan melalui daftar, bukan mengelilinginya: delapan kartu pertama adalah HTML, sisanya JSON.

Modul pilar dikirim dari halaman server sebagai enum yang sudah valid, bukan dibaca dari query string di klien. Endpoint-nya read-only, page dan limit dibatasi di server, dan tidak ada input pengguna yang dipantulkan ke request. Ini yang membuat island tetap aman meski ia bicara langsung ke API publik.

Dedupe bukan hiasan

Ada satu detail kecil yang gampang dihapus saat review: sebelum menambahkan halaman baru, kode membangun Set dari id baris yang sudah tampil, lalu menyaring hasil fetch dengan has(). Objek Set menyimpan nilai unik, dan pencarian di dalamnya rata-rata lebih cepat daripada pencarian di array dengan ukuran yang sama [4].

Alasannya bukan performa. Paginasi berbasis offset bergeser begitu ada entitas baru masuk di antara dua fetch. Halaman 2 yang diambil lima menit setelah halaman 1 bisa memuat ulang satu kartu yang sudah tampil di halaman 1. Tanpa dedupe, pembaca melihat kartu kembar dan menyimpulkan datanya rusak, padahal datanya benar dan urutannya yang bergeser.

Tombol dan penghitung "Menampilkan 8 dari 24 entitas" disembunyikan sepenuhnya begitu jumlah baris mencapai total, jadi tidak ada tombol mati yang bisa diklik berkali-kali. Kalau fetch gagal, muncul satu baris peringatan dengan role="alert" dan tombolnya bisa dicoba lagi. Tiga halaman pilar turun ke 8, arus video tetap 12 karena daftar putar memang menggulir sendiri di tempatnya.

Yang saya bawa dari perubahan ini bukan pola komponennya. Polanya sudah ada di halaman lain di repo yang sama, saya hanya menyalinnya. Yang berubah adalah cara saya membaca batas: setiap kali sebuah daftar dirender penuh oleh server, pertanyaan pertamanya bukan "berapa banyak yang muat", tapi "berapa banyak yang boleh menahan render halaman ini". Delapan kartu pertama cukup untuk membuat halaman terlihat penuh. Sisanya bisa menyusul.

Sumber

Artikel terkait