Minified React Error adalah error yang biasanya terjadi pada website berbasis Rendering di server seperti SSR, SSG, DSG, atau ISR pada framework kayak GatsbyJS, React, NextJS, dan sejenisnya. Biasanya terdapat 3 error: Minified React error #425, Minified React error #418, dan Minified React error #423. Ketiga error tersebut root cause-nya umumnya sama. Inti dari permasalahan ketiganya adalah terjadi perbedaan antara konten hasil build dari server dengan konten awal yang dimuat di browser.
Hydration
Sebelum kita telaah lebih jauh solusinya, kita perlu pahami dulu masalahnya, yaitu Hydration. Hydration adalah proses transisi dari halaman yang tadinya statis menjadi interaktif. Pada saat browser pertama kali load sebuah halaman berbasis Rendering di server, sebenarnya server melakukan generate konten halamannya dengan mengeksekusi JS di server dan menghasilkan output halaman statis berupa HTML & CSS doang. Jadi, saat browser pertama kali menerima response, ga ada JS dan belum bisa mengeksekusi hal-hal terkait JS. Lalu server akan mengirimkan JS secara terpisah dan barulah browser mengeksekusi JS yang diterima hingga website tersebut menjadi interaktif. Itulah yang disebut dengan Hydration. Makanya website tersebut loading awalnya jadi cukup cepat dan SEO-nya cenderung bagus.
Problem
Masalahnya, kalau logic saat build/render di server dan logic saat Hydration di browser hasilnya beda, maka akan error. Perbedaan itu terjadi karena ada logic yang hasilnya dinamis, seperti tampilan waktu, hasil dari function Math.Random()
, atau hal lainnya. Untuk memastikannya, cek tools network di browser, lalu hard refresh halaman tersebut, dan lihat response HTML di paling atas. Kemudian bandingkan dengan tampilan yang muncul di web dan cari perbedaannya. Di kasus yang gw hadapi, masalahnya di bagian tanggal. Di server gw menggunakan UTC. Sedangkan timezone browser gw dan data tanggal yang gw simpan adalah Asia/Jakarta (+07:00). Jadinya saat pertama load atau hard refresh, muncul waktu dalam zona UTC sesuai hasil generate di server, lalu sepersekian detik kemudian terjadi “quick flash” dan berubah menjadi waktu Asia/Jakarta sesuai waktu browser. Ketidaksamaan hasil tampilan dari server dengan browser itu yang mengakibatkan error. Kalau run di localhost biasanya ga kejadian karena timezone di local dengan browser harusnya sama.
Timezone Solution
Kalau kasusnya spesifik terkait timezone seperti yang gw alami, bisa dengan mendefinisikan timezone sebelum digunakan. Contoh kasusnya ketika menggunakan date-fns untuk mengkonversi tanggal dengan format tertentu seperti ini:
function formatToPattern(dateArg, formatString) {
return format(dateArg, formatString);
}
Function tersebut menerima value dateArg seperti “2023-04-18T03:00+07:00”. Oleh library date-fns, tanggal tersebut akan di-convert menggunakan objek Date, baik di server maupun di browser nanti. Hasilnya tentu saja bakal berbeda karena servernya belum tentu timezone-nya sama dengan timezone browser yang mengakses halaman tersebut. Inilah sumber masalahnya.
Untuk memperbaikinya, kita perlu convert tanggalnya dengan locale string beserta option timezone yang ingin dijadikan patokan seperti berikut agar apapun timezonenya nanti di browser maupun di server hasilnya jadi selaras dan ga error.
function formatToPattern(dateArg, formatString) {
const date = typeof dateArg === "string" ? new Date(dateArg) : dateArg;
const zonedDate = date.toLocaleString("en-US", { timeZone: "Asia/Jakarta" });
return format(zonedDate, formatString);
}
Setelah melakukan hal di atas, silakan commit, deploy, dan liat langsung hasilnya di server😎.
Wrapper Component Solution
Untuk kasus lainnya yang bukan terkait tanggal bisa dengan menambahkan logic untuk menampilkan placeholder yang kita inginkan saat pertama kali load. Sehingga baik di sisi server maupun browser saat pertama kali load kontennya berupa placeholder yang sama dan terhindar dari error. Konten aslinya akan di-load belakangan di sisi browser saja setelah Hydration. Contoh di kasus gw adalah ketika menampilkan logic updated date dengan value kalimat jarak waktu antara waktu terakhir perubahan dengan waktu browser saat ini.
<span className="page-info">
{`updated ${formatDistanceToNow(updatedAt)} ago`}
</span>
Code di atas akan error karena di dalam logic formatDistanceToNow()
di function date-fns terdapat logic menggunakan Date.now()
yang dapat membuat hasil eksekusi di server dan browser berbeda.
Solusinya kita akan membuat component khusus yaitu ClientSide
sebagai container untuk membungkus bagian yang hanya akan di-generate di sisi browser aja. Kita akan menggunakan useState()
& useEffect()
untuk mengecek statenya saat pertama kali load di browser lewat variable onClient
. Kita set initial valuenya jadi false
. Kita akan menampilkan placeholder “…” saat statenya masih false waktu dieksekusi di server dan Hydration di browser. Lalu setelah Hydration di browser state-nya akan berganti menjadi true
dan menampilkan konten sesuai yang kita inginkan. Code-nya seperti berikut:
function ClientSide({ children }) {
const [onClient, setOnClient] = useState(false);
useEffect(() => {
setOnClient(true);
}, []);
if (onClient) {
return <>{children}</>;
}
return <>…</>;
}
Kemudian kita ganti code di bagian render-nya dengan membungkus bagian yang hanya ingin di-generate di sisi browser saja menggunakan component ClientSide
tadi seperti berikut:
<ClientSide>
<span className="page-info">
{`updated ${formatDistanceToNow(updatedAt)} ago`}
</span>
</ClientSide>
Karakter “…” akan muncul saat render di server maupun saat Hydration di browser. Setelah proses Hydration, browser akan generate ulang konten yang ada di dalam ClientSide
. Sekarang errornya sudah tidak ada lagi di console.log
dan konten yang muncul sesuai dengan yang diinginkan😎. Kita juga bisa reuse component itu di berbagai code yang hasil rendernya dinamis, jadi ga perlu bikin lagi tiap kasus.