Global Error Handler
Menangkap error di root layout dengan global-error.tsx.
Kenapa error.tsx biasa tidak cukup
Di App Router, error.tsx bekerja sebagai error boundary untuk segmen tempat ia berada. Bayangkan gedung bertingkat: error.tsx di lantai dua hanya menangkap kebakaran di lantai dua dan di atasnya, bukan di fondasi gedung.
Root layout (app/layout.tsx) adalah fondasinya: ia me-render tag <html> dan <body> untuk seluruh aplikasi. Kalau error terjadi di root layout (misalnya provider theme crash saat inisialisasi, atau font gagal dimuat), error.tsx biasa tidak bisa menangkapnya, karena boundary-nya berada di dalam layout yang sedang error itu sendiri. Hasilnya: halaman putih kosong.
Untuk fondasi inilah global-error.tsx ada. Ia menggantikan root layout sepenuhnya saat fondasi runtuh.
Contoh 1: global-error.tsx yang lengkap
// app/global-error.tsx
"use client";
import { useEffect } from "react";
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Kirim ke layanan monitoring, jangan cuma console.log di production
console.error("Global error:", error);
}, [error]);
return (
<html lang="id">
<body style={{ fontFamily: "system-ui", textAlign: "center", padding: "4rem 1rem" }}>
<h1>Yah, aplikasinya crash</h1>
<p>
Terjadi kesalahan yang tidak bisa dipulihkan di bagian utama
aplikasi. Coba muat ulang, biasanya itu menyelesaikan masalah.
</p>
<button onClick={() => reset()}>Muat ulang halaman</button>
{process.env.NODE_ENV === "development" && (
<pre style={{ textAlign: "left", marginTop: "2rem" }}>
{error.message}
</pre>
)}
</body>
</html>
);
}Dua hal wajib diperhatikan. Pertama, file ini harus me-render <html> dan <body> sendiri, karena root layout yang asli sedang rusak dan tidak bisa dipakai. Itu juga alasan file ini wajib "use client": ia butuh onClick untuk tombol reset dan useEffect untuk logging. Kedua, reset() mencoba me-render ulang segmen yang error, jadi user tidak harus refresh manual lewat browser.
Detail kecil tapi penting: pesan error mentah (error.message) hanya ditampilkan di development. Di production, user cukup melihat pesan ramah, sementara detail teknisnya dikirim ke monitoring.
Contoh 2: kapan global-error tampil vs error.tsx biasa
// app/error.tsx (boundary biasa, posisinya DI DALAM root layout)
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<main style={{ textAlign: "center", padding: "4rem 1rem" }}>
<h2>Ada yang salah di halaman ini</h2>
<p>Tenang, bagian lain aplikasi tetap aman.</p>
<button onClick={() => reset()}>Coba lagi</button>
</main>
);
}Aturan mainnya sederhana, error ditangkap oleh boundary terdekat yang berada di atas lokasi error:
- Error di
app/blog/page.tsxditangkapapp/blog/error.tsxkalau ada, kalau tidak ada naik keapp/error.tsx. - Error di
app/layout.tsxsendiri (misalnya di dalam<ThemeProvider>) tidak bisa ditangkapapp/error.tsx, karena boundary itu ada di dalamnya. Yang tampil adalahapp/global-error.tsx.
Kalau kamu bingung file mana yang akan tampil, tanyakan: "apakah root layout masih utuh?" Kalau ya, error.tsx biasa cukup. Kalau root layout-nya sendiri yang rusak, hanya global-error.tsx yang bisa menyelamatkan.
Kapan benar-benar dibutuhkan
Di project kecil atau tugas sekolah, file ini hampir tidak pernah tampil, dan itu tidak apa-apa. Tapi untuk aplikasi production yang dipakai orang banyak, halaman putih kosong adalah skenario terburuk: user mengira internetnya rusak, tidak ada pesan, tidak ada jalan keluar.
Satu catatan production yang penting: UI boleh ramah dan sederhana, tapi error-nya wajib terkirim ke monitoring (Sentry, LogRocket, atau endpoint log milikmu sendiri). User melihat "coba muat ulang", tim engineering melihat stack trace lengkap. Keduanya harus terjadi di file yang sama, jangan sampai salah satunya terlewat.
Tantangan
Uji global error
Tambahkan app/global-error.tsx sederhana. Jelaskan dengan kata-katamu kapan file ini tampil vs error.tsx biasa.