Belajar Remix - Error Handling & UX
Episode 17 of 24

Belajar Remix - Error Handling & UX

Episode ini membahas penanganan error di Remix: error boundaries untuk menangkap kegagalan render, halaman error yang ramah pengguna, fallback UI untuk state pending dan error, serta pelaporan error di level request.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Test di episode 16 menjaga hal-hal yang diketahui. Tapi aplikasi juga gagal dengan cara yang tidak terduga: database down, API eksternal lambat, kode yang melempar error di jam 3 pagi. Episode 17 memastikan kegagalan itu tidak mengubah aplikasi menjadi layar putih yang kosong.

Remix memiliki model error handling yang dirancang baik: setiap route bisa mengekspor ErrorBoundary untuk menangkap error rendering, dan CatchBoundary pada Remix v2 untuk respons HTTP. Saat error terjadi, Remix me-render fallback yang kalian definisikan — bukan halaman kosong.

Episode 17 akan membangun strategi error lengkap: boundary per route, halaman error yang ramah, fallback untuk state pending, dan pelaporan error ke layanan monitoring.

Error Boundaries dan Catch Boundaries

ErrorBoundary untuk Rendering

ErrorBoundary menangkap error yang terjadi saat komponen di-render di route tersebut. Ia menerima error dan menampilkan fallback UI:

JSError boundary sederhana
export function ErrorBoundary({ error }) {
  console.error(error);
  return (
    <div>
      <h1>Terjadi kesalahan</h1>
      <p>Maaf, ada yang tidak beres saat memuat halaman ini.</p>
    </div>
  );
}

ErrorBoundary menerima error dan me-render UI pengganti di dalam route itu. Error yang tidak ditangani route akan naik ke root ErrorBoundary di app/root.tsx.

Perbedaan Boundary di Remix v2 dan v3

Di Remix v2, CatchBoundary menangani respons 4xx dan 5xx yang dilempar loader, sementara ErrorBoundary menangani error tak terduga. Di Remix v3, CatchBoundary dihapus — semua error ditangani oleh ErrorBoundary tunggal dengan memeriksa response.status. Saat membaca tutorial lama, perhatikan versi yang dipakai agar tidak bingung.

User-Friendly Error Pages

Jangan Menakut-nakuti Pengguna

Halaman error yang baik menjelaskan apa yang terjadi tanpa jargon teknis, memberi jalan keluar, dan tidak pernah menampilkan stack trace. Kombinasi yang aman: judul singkat, pesan ringkas, dan tombol kembali atau muat ulang.

JSHalaman error dengan aksi
import { Link } from "@remix-run/react";
 
export function ErrorBoundary({ error }) {
  return (
    <main>
      <h1>Halaman tidak bisa dimuat</h1>
      <p>Silakan coba lagi dalam beberapa saat.</p>
      <Link to="/">Kembali ke beranda</Link>
    </main>
  );
}

Aksi yang jelas seperti Link membuat pengguna tidak buntu di halaman error. Beranda adalah tujuan pulang yang selalu aman.

Error di Level Route vs Root

Definisikan ErrorBoundary di route yang rawan gagal — halaman detail, dasbor — dan biarkan root menyediakan jaring pengaman terakhir. Pendekatan berlapis memberi kontrol lokal tanpa kehilangan keamanan global.

Fallback UI untuk State Pending dan Error

State Pending dari Fetcher

useFetcher mengekspos fetcher.state dengan nilai idle, submitting, dan loading. UI bisa memakai ini untuk menampilkan indikator:

JSFallback untuk state submitting
import { useFetcher } from "@remix-run/react";
 
export default function FormKirim() {
  const fetcher = useFetcher();
  const sedangKirim = fetcher.state !== "idle";
 
  return (
    <fetcher.Form method="post">
      <button type="submit" disabled={sedangKirim}>
        {sedangKirim ? "Mengirim..." : "Kirim"}
      </button>
    </fetcher.Form>
  );
}

fetcher.state berubah sesuai siklus pengiriman; tombol nonaktif mencegah klik ganda. Feedback visual seperti ini membuat antrean terasa dikelola, bukan dibiarkan.

Fallback untuk Data yang Lambat

Untuk data yang di-stream dengan defer, Await menyediakan fallback sementara data belum siap. Gabungkan dengan state error di dalam Await untuk menampilkan pesan bila promise gagal. Setiap promise yang di-stream butuh fallback pending dan error.

Request-Level Error Reporting

Mencatat Error di Server

Error di loader dan action terjadi di server — di sanalah kalian mencatatnya. Jangan hanya mencatat di console; kirim ke layanan seperti Sentry. Praktik mencatat error lengkap dengan request dan pengguna akan dibahas lagi di episode 22.

JSMencatat error sebelum rethrow
export async function loader() {
  try {
    const data = await ambilData();
    return data;
  } catch (error) {
    console.error("Gagal memuat data", error);
    throw error;
  }
}

Catat error di titik paling dekat sumbernya, lalu rethrow agar boundary menanganinya. Pelaporan yang baik memberi konteks: kapan, di route mana, dan dengan request seperti apa.

Membangun Budaya Error

Error yang nyaman dilihat adalah tanda aplikasi dewasa. Simpan catatan error sebagai sumber belajar tim, bukan aib. Setiap kegagalan yang ditangani dengan baik adalah pelajaran yang terdokumentasi.

Penutup

Episode 17 membuat aplikasi kalian kuat menghadapi kegagalan: ErrorBoundary per route dan global, halaman error yang ramah pengguna, fallback untuk state pending dan error, serta pelaporan error di level request. Kegagalan kini menjadi kejadian yang dikelola, bukan krisis.

Inti yang harus dibawa pulang:

  • ErrorBoundary menangkap error render; di v3 semua error jatuh ke satu boundary.
  • Halaman error harus ringkas, jelas, dan memberi jalan keluar.
  • Jangan pernah menampilkan stack trace kepada pengguna.
  • fetcher.state memberi fallback visual untuk submitting dan loading.
  • Setiap promise yang di-stream butuh fallback pending dan error.
  • Catat error di server dengan konteks, lalu kirim ke layanan monitoring.

Di episode 18 selanjutnya kita akan membahas architecture dan maintainability — struktur folder untuk aplikasi Remix yang skalabel, organisasi feature modules dan route, shared utilities serta typed contracts, dan cara menjaga kode tetap terpelihara. Error sudah ditangani; sekarang pastikan kode yang menangani itu mudah dirawat.