Flicker UI pada aplikasi CodeIgniter 4 biasanya terjadi ketika HTML awal yang dikirim server sudah menampilkan satu state, tetapi setelah JavaScript berjalan di browser, state itu berubah. Hasilnya adalah tampilan berkedip, elemen muncul-lalu-hilang, teks berubah sesaat, atau layout bergeser sebelum akhirnya stabil.
Masalah ini bukan sekadar kosmetik. Flicker menandakan ada sumber kebenaran state yang tidak konsisten antara render awal di server dan inisialisasi di client. Jika tidak ditangani, efeknya bisa merusak pengalaman pengguna, menyulitkan debugging, dan memicu bug logika yang lebih serius pada komponen kondisional seperti menu login, tema, locale, atau data personalisasi.
Memahami alur render sederhana di CodeIgniter 4
Pada alur yang umum, CodeIgniter 4 menerima request, membaca session atau data lain di controller, lalu merender view menjadi HTML. Setelah HTML diterima browser, JavaScript dijalankan dan sering kali menghitung ulang state dari sumber lain seperti localStorage, waktu lokal, locale browser, atau request async tambahan.
Secara sederhana alurnya seperti ini:
- Request masuk ke controller CodeIgniter 4.
- Controller mengambil data server-side, misalnya session user, preferensi, atau hasil query.
- View dirender menjadi HTML dengan state awal tertentu.
- Browser menerima HTML dan menampilkannya.
- JavaScript inisialisasi berjalan dan bisa mengubah state awal berdasarkan data client.
Flicker terjadi jika langkah 3 dan 5 menghasilkan state yang berbeda.
Contoh gejala yang sering terlihat
- Tombol Login tampil sesaat lalu berubah menjadi nama user.
- Sidebar default tertutup di HTML, tetapi langsung terbuka setelah script memuat preferensi dari
localStorage. - Tema terang dirender server, lalu berganti ke tema gelap saat JavaScript membaca preferensi client.
- Format tanggal/jam berubah sesaat karena server dan browser menggunakan locale atau timezone berbeda.
- Elemen premium/admin sempat terlihat atau sempat hilang sebelum data role selesai dimuat.
Penyebab utama state awal tidak konsisten
1. Default state di server dan JavaScript berbeda
Ini penyebab paling umum. Server merender satu nilai default, sementara JavaScript punya default lain saat state belum tersedia.
Contoh: server menganggap sidebar tertutup, tetapi script client menginisialisasi open = true.
<!-- View server -->
<aside id="sidebar" class="sidebar" data-open="0">...</aside>
<script>
// Default client berbeda: langsung true
const isOpen = true;
document.getElementById('sidebar').classList.toggle('is-open', isOpen);
</script>Akibatnya, sidebar tampil tertutup lalu terbuka sesaat setelah script dijalankan.
2. Data user atau session terlambat dimuat
Server mungkin belum menggunakan semua data yang nanti dipakai JavaScript, atau sebaliknya JavaScript baru mengambil data user melalui request tambahan setelah halaman tampil.
Contoh anti-pattern:
- HTML awal menampilkan menu tamu.
- JavaScript memanggil endpoint
/api/me. - Jika user sudah login, menu diganti menjadi menu akun.
Secara teknis ini valid, tetapi jika informasi login sebenarnya sudah tersedia di server melalui session, pola ini menimbulkan flicker yang seharusnya bisa dihindari.
3. Elemen kondisional bergantung pada state yang hanya diketahui di client
Elemen seperti banner, modal, tab aktif, atau filter sering disembunyikan/ditampilkan berdasarkan state awal. Jika state itu dihitung dari URL hash, ukuran viewport, atau storage browser tanpa placeholder yang stabil, UI bisa berubah setelah paint pertama.
4. Penggunaan localStorage tanpa sinkronisasi dengan render awal
localStorage hanya tersedia di browser, bukan saat server merender HTML. Jika Anda menyimpan preferensi seperti tema, mode sidebar, atau locale di sana, server tidak bisa mengetahui nilainya kecuali Anda mengirimkannya ke server sebelumnya melalui cookie, form, atau API.
Karena itu, pola seperti ini sering menyebabkan flicker:
<body class="theme-light">
...
<script>
const theme = localStorage.getItem('theme') || 'dark';
document.body.classList.remove('theme-light');
document.body.classList.add('theme-' + theme);
</script>Server mengirim theme-light, tetapi client mengganti ke theme-dark setelah halaman tampil.
5. Perbedaan waktu, timezone, atau locale antara server dan browser
Server bisa memformat tanggal dengan locale atau timezone default server, sementara browser memformat ulang berdasarkan locale pengguna. Perubahan seperti "14/06/2026 08:00" menjadi "Jun 14, 2026, 3:00 PM" mungkin terlihat kecil, tetapi tetap merupakan perubahan state visual.
Kasus ini sering muncul pada:
- Label waktu relatif seperti baru saja, 5 menit lalu.
- Format tanggal lokal.
- Konten yang sensitif terhadap zona waktu.
Contoh masalah sebelum diperbaiki
Misalkan Anda ingin menampilkan status login dan nama user di header.
Sebelum: server render sebagai tamu, client koreksi setelah fetch
<!-- app/Views/layout/header.php -->
<nav>
<span id="nav-user">Masuk</span>
</nav>
<script>
fetch('/api/me', { credentials: 'include' })
.then(r => r.ok ? r.json() : null)
.then(data => {
if (data && data.name) {
document.getElementById('nav-user').textContent = data.name;
}
});
</script>Jika user sudah login, halaman akan menampilkan Masuk sesaat lalu berubah menjadi nama user. Ini flicker klasik karena state awal tidak memanfaatkan session yang sebenarnya sudah tersedia di server.
Strategi utama: pastikan server dan client memakai state awal yang sama
Prinsip paling aman adalah: jika server sudah tahu state-nya, render state itu di HTML dan kirim state yang sama ke JavaScript sebagai initial data. Jangan biarkan JavaScript menebak ulang dengan default berbeda.
Sesudah: inject initial state dari server
Controller:
<?php
namespace App\Controllers;
class Dashboard extends BaseController
{
public function index()
{
$session = session();
$user = null;
if ($session->get('isLoggedIn')) {
$user = [
'name' => $session->get('name'),
'role' => $session->get('role'),
];
}
$initialState = [
'user' => $user,
'sidebarOpen' => false,
'locale' => service('request')->getLocale(),
];
return view('dashboard', [
'initialState' => $initialState,
]);
}
}View:
<?php $jsonState = json_encode($initialState, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); ?>
<nav>
<span id="nav-user">
<?= esc($initialState['user']['name'] ?? 'Masuk') ?>
</span>
</nav>
<script id="initial-state" type="application/json"><?= $jsonState ?></script>
<script>
const initialStateEl = document.getElementById('initial-state');
const initialState = JSON.parse(initialStateEl.textContent);
const navUser = document.getElementById('nav-user');
navUser.textContent = initialState.user ? initialState.user.name : 'Masuk';
</script>Dengan pola ini, HTML awal dan JavaScript memakai sumber data yang sama. Tidak ada perubahan visual yang tidak perlu saat inisialisasi.
Mengapa pola ini bekerja
- Server dan client membaca initial state yang identik.
- JavaScript tidak perlu menebak default sendiri.
- Data session yang sudah ada di server dimanfaatkan sejak render pertama.
- Risiko mismatch lebih kecil karena satu sumber data dipakai lintas layer.
Pola inject data awal via JSON yang aman
Menyisipkan data awal ke HTML adalah praktik umum, tetapi lakukan dengan hati-hati. Hindari membuat string JavaScript mentah dari data user tanpa encoding yang tepat.
Jangan lakukan ini
<script>
window.APP_STATE = {
name: '= $userName ?>'
};
</script>Jika $userName mengandung karakter khusus, kutip, atau input tak terduga, script bisa rusak atau membuka peluang XSS bila sanitasi salah.
Pilih ini
<?php
$state = [
'user' => [
'name' => $userName,
],
];
?>
<script id="initial-state" type="application/json">
= json_encode($state, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>
</script>Kemudian parse di client:
const state = JSON.parse(document.getElementById('initial-state').textContent);Catatan: Tetap hanya kirim data yang memang perlu di browser. Jangan menaruh token sensitif, secret internal, atau seluruh isi session ke initial state.
Menangani localStorage, theme, dan preferensi client
Ada kasus ketika state memang hanya diketahui di browser, misalnya preferensi tema yang belum pernah dikirim ke server. Dalam situasi ini, Anda punya dua pilihan utama.
Pilihan 1: sinkronkan preferensi ke server
Jika preferensi penting untuk render awal, simpan nilainya juga di server atau cookie yang dapat dibaca sebelum HTML dirender. Dengan begitu CodeIgniter 4 bisa menghasilkan HTML yang sesuai sejak awal.
Contoh pendekatan:
- Saat user mengganti tema, simpan ke
localStoragedan kirim ke endpoint untuk disimpan pada profil user. - Atau simpan juga ke cookie agar request berikutnya membawa preferensi itu.
Ini cocok jika Anda ingin render awal stabil dan preferensi dipakai lintas perangkat atau lintas halaman.
Pilihan 2: render placeholder stabil, lalu upgrade di client
Jika state memang tidak layak atau tidak praktis diketahui server, jangan render state final yang berisiko salah. Render placeholder yang netral dan stabil, lalu isi setelah data client tersedia.
Contoh untuk tema atau panel personalisasi:
<body class="theme-pending">
<div id="account-panel" hidden></div>
<script>
const theme = localStorage.getItem('theme') || 'light';
document.body.classList.remove('theme-pending');
document.body.classList.add('theme-' + theme);
const panel = document.getElementById('account-panel');
panel.hidden = false;
</script>Pendekatan ini lebih baik daripada menampilkan state yang salah lalu menggantinya, terutama untuk elemen kondisional yang sensitif.
Kapan memilih placeholder stabil
- State hanya tersedia di browser dan tidak ada sinkronisasi ke server.
- Perubahan visual final kecil dan tidak kritis untuk interaksi awal.
- Anda ingin menghindari salah render, misalnya menu role-based atau panel user.
- Biaya sinkronisasi state ke server lebih besar daripada manfaatnya.
Kasus waktu dan locale: jangan format dua kali dengan asumsi berbeda
Jika format tanggal/jam penting, pilih salah satu sumber kebenaran untuk render awal.
Pendekatan yang lebih aman
- Server merender format final dan client tidak memformat ulang elemen yang sama.
- Atau server merender placeholder/netral, lalu client memformat setelah locale diketahui.
- Atau kirim data mentah, misalnya ISO timestamp, lalu pakai aturan formatting yang konsisten.
Contoh lebih stabil:
<time datetime="2026-06-14T08:00:00Z" data-time="2026-06-14T08:00:00Z">14 Jun 2026 08:00 UTC</time>Jika client ingin mengganti format sesuai locale browser, pertimbangkan untuk menyembunyikan format final sampai script siap, atau gunakan format netral yang masih dapat diterima sebelum enhancement dilakukan.
Langkah diagnosis saat menemukan flicker UI
1. Bandingkan HTML awal dengan DOM setelah JavaScript berjalan
Buka View Source untuk melihat HTML murni dari server, lalu bandingkan dengan DOM di DevTools setelah halaman selesai dimuat. Jika isi elemen berbeda, kemungkinan besar ada mismatch state awal.
2. Cari state default ganda
Periksa apakah ada nilai default di dua tempat:
- di controller/view server
- di file JavaScript client
Jika keduanya tidak berasal dari sumber yang sama, itulah kandidat utama penyebab flicker.
3. Audit data yang datang terlambat
Lihat Network tab dan cari request async yang mengubah UI setelah paint pertama, misalnya /api/me, /settings, atau endpoint preferensi. Tanyakan: apakah data ini sebenarnya sudah bisa diketahui saat render server?
4. Telusuri pembacaan localStorage, timezone, dan locale browser
Cari penggunaan:
localStorage.getItem(...)Intl.DateTimeFormatnavigator.languagenew Date()untuk label relatif
Jika hasil dari API ini memengaruhi elemen yang sudah dirender server, potensi flicker tinggi.
5. Tambahkan logging state awal
Untuk debugging, cetak state yang dirender server dan state yang dipakai client saat bootstrap. Misalnya:
console.log('initialState from server', initialState);
console.log('theme from storage', localStorage.getItem('theme'));Tujuannya bukan logging permanen, tetapi memastikan asumsi Anda benar.
Checklist debugging CodeIgniter 4 untuk masalah flicker UI
- Apakah HTML awal dari server sudah mencerminkan state yang benar?
- Apakah JavaScript punya default state yang berbeda dari server?
- Apakah data login/user sebenarnya sudah tersedia di session tetapi tetap di-fetch ulang setelah render?
- Apakah elemen kondisional ditentukan oleh data yang baru tersedia di browser?
- Apakah Anda membaca
localStorageuntuk menentukan tampilan awal tanpa fallback yang stabil? - Apakah ada perbedaan locale/timezone antara server dan browser?
- Apakah data awal untuk client disisipkan dengan aman via JSON, bukan string inline rapuh?
- Apakah Anda mengirim terlalu banyak data sensitif ke browser?
- Apakah placeholder lebih masuk akal daripada render final yang berisiko salah?
Pola perbaikan yang umumnya paling efektif
1. Jadikan server sebagai sumber state awal jika data sudah tersedia
Untuk session user, role, locale aplikasi, flag fitur, atau data query yang sudah didapat di controller, render langsung di view dan kirim ulang state yang sama ke JavaScript.
2. Hindari fetch ulang hanya untuk mengoreksi UI awal
Request async tetap berguna, tetapi jangan dipakai untuk memperbaiki state awal yang seharusnya sudah benar. Gunakan request tambahan hanya untuk data yang memang belum ada atau perlu diperbarui setelah halaman tampil.
3. Gunakan placeholder stabil untuk state yang hanya diketahui di client
Jika browser adalah satu-satunya sumber kebenaran, tampilkan UI netral atau skeleton kecil daripada state final yang mungkin salah.
4. Simpan preferensi penting ke media yang bisa dibaca server
Jika tema, locale, atau preferensi layout memengaruhi tampilan awal secara signifikan, simpan juga ke cookie atau backend supaya render awal bisa konsisten.
5. Minimalkan transformasi ulang pada elemen yang sudah final
Jika server sudah merender nama user, label menu, atau status autentikasi dengan benar, JavaScript tidak perlu mengganti lagi kecuali ada perubahan nyata.
Kesalahan umum yang sering terjadi
- Menganggap flicker sebagai masalah CSS, padahal sumbernya mismatch state.
- Menggunakan
display: nonesecara agresif untuk menutupi gejala, bukan memperbaiki penyebab. - Mengirim seluruh objek session ke browser agar mudah, yang berisiko dari sisi keamanan dan coupling.
- Mengandalkan
localStorageuntuk semua state awal tanpa strategi sinkronisasi. - Membiarkan server dan client memformat tanggal secara independen dengan aturan berbeda.
Kesimpulan
Untuk mengatasi flicker UI dari state awal yang tidak konsisten di CodeIgniter 4, fokus utamanya adalah menyamakan state antara HTML hasil render server dan inisialisasi JavaScript di browser. Jika server sudah tahu nilainya, render nilainya sejak awal dan kirim ke client sebagai initial state yang aman melalui JSON. Jika state hanya tersedia di browser, lebih baik tampilkan placeholder yang stabil daripada merender state final yang berpotensi salah.
Dengan pendekatan ini, UI menjadi lebih tenang, prediktif, dan mudah dirawat. Selain memperbaiki pengalaman pengguna, Anda juga mendapatkan alur data yang lebih jelas antara controller, view, dan script client.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!