Insiden ChunkLoadError atau Failed to fetch dynamically imported module merupakan salah satu masalah paling umum pada aplikasi Nuxt 3 berbasis Single Page Application (SPA) maupun Server-Side Rendering (SSR) saat proses rolling deployment berlangsung. Pengguna yang sedang aktif membuka aplikasi tiba-tiba mendapati navigasi halaman macet atau layar putih (blank screen).

Akar Masalah: Dynamic Import dan Version Skew

Nuxt 3 menggunakan Vite (atau Rollup) untuk memecah bundle JavaScript menjadi potongan-potongan kecil (code splitting). Setiap rute dan komponen asinkron menghasilkan file terpisah dengan identifikasi hash konten, misalnya _nuxt/entry.B3x89aQz.js.

Ketika proses deployment baru dijalankan:

  1. Server atau container baru hanya menyimpan file build hasil commit terakhir (misal: entry.C9k11bWp.js).
  2. Klien yang masih membuka tab browser versi lama masih memegang manifest build lama di memory.
  3. Saat pengguna berpindah rute, runtime browser mengeksekusi import("/_nuxt/entry.B3x89aQz.js").
  4. Server merespons dengan status HTTP 404 Not Found karena file tersebut sudah terhapus di server baru.
  5. Browser melempar kegagalan promise dynamic import: ChunkLoadError.

Fenomena hilangnya sinkronisasi antara versi HTML/JS yang dimuat oleh klien dan versi aset yang tersedia di server dikenal sebagai version skew.

Observability: Deteksi Lonjakan Error via Sentry

Jangan menunggu laporan pengguna di kanal dukungan pelanggan. Deteksi insiden ini secara otomatis menggunakan integrasi pemantauan error.

1. Filter dan Normalisasi Pesan Error

Pesan error bervariasi tergantung engine browser. Chromium menghasilkan Failed to fetch dynamically imported module, sedangkan Webpack/Vite legacy melempar Loading chunk [id] failed.

Kelompokkan error ini pada Sentry menggunakan fingerprinting custom agar tidak menyampah di dashboard dan terhitung sebagai satu kluster insiden:

// sentry.client.config.ts
Sentry.init({
  dsn: useRuntimeConfig().public.sentryDsn,
  beforeSend(event, hint) {
    const error = hint.originalException;
    const errorMessage = error instanceof Error ? error.message : String(error);

    const isChunkError =
      errorMessage.includes("Failed to fetch dynamically imported module") ||
      errorMessage.includes("Loading chunk") ||
      errorMessage.includes("error loading dynamically imported module");

    if (isChunkError) {
      event.fingerprint = ["nuxt-chunk-load-error"];
      event.tags = {
        ...event.tags,
        error_subtype: "chunk_load_failure",
      };
    }

    return event;
  },
});

2. Konfigurasi Alert Threshold

Konfigurasikan alert metric di Sentry atau Datadog dengan kriteria:

  • Query: tags.error_subtype:chunk_load_failure
  • Threshold: Volume kejadian > 50 events dalam jendela waktu 5 menit, atau lonjakan 3x lipat di atas baseline paska trigger deployment webhook.

Kapan Harus Rollback? Matriks Keputusan

Melakukan rollback secara gegabah sering kali memperburuk situasi. Jika Anda melakukan rollback ke versi lama saat separuh pengguna sudah memuat versi baru, Anda menciptakan gelombang ChunkLoadError baru bagi pengguna versi baru tersebut.

Aturan Utama: Jangan lakukan rollback jika build baru berjalan normal dan error hanya menimpa klien lama. Lakukan rollback HANYA jika build baru rusak total (misalnya runtime backend melempar HTTP 500).

  • Kondisi A: Server HTTP 500, crash pod, fatal error di build baru → ROLLBACK SEGERA.
  • Kondisi B: Server HTTP 200 normal, tetapi lonjakan HTTP 404 pada chunk _nuxt/*.js lama → JANGAN ROLLBACK. Jalankan mitigasi retensi CDN atau trigger purge terarah.

Pencegahan Arsitektural: Sinkronisasi Multi-Versi ke CDN

Pencegahan permanen terhadap version skew adalah memisahkan siklus hidup aset statis (JS/CSS) dari container runtime aplikasi.

1. Nonaktifkan Flag Penghapusan Otomatis di CI/CD

Ketika mengunggah artefak build ke Object Storage (S3, Cloudflare R2, Google Cloud Storage), jangan pernah menggunakan flag --delete.

# CONTOH SALAH (Menghapus aset lama seketika)
aws s3 sync .output/public/_nuxt s3://my-app-cdn/_nuxt/ --delete

# CONTOH BENAR (Menimpa aset baru, membiarkan aset lama tetap hidup)
aws s3 sync .output/public/_nuxt s3://my-app-cdn/_nuxt/ \
  --cache-control "public,max-age=31536000,immutable"

Buat Lifecycle Rule pada bucket storage untuk menghapus file yang tidak dimodifikasi setelah 7 atau 14 hari. Durasi ini lebih dari cukup untuk memastikan tab browser pengguna lama sudah ditutup atau di-refresh.

2. Hubungkan Nuxt dengan CDN URL

Arahkan permintaan static chunk langsung ke CDN melalui konfigurasi nuxt.config.ts:

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    cdnURL: process.env.NUXT_APP_CDN_URL || "https://cdn.example.com/",
    buildAssetsDir: "_nuxt/",
  },
});

Mitigasi Client-Side: Hook app:chunkError

Jika pengguna tetap mengalami kegagalan load chunk (misalnya karena koneksi internet putus sejenak atau aset CDN terlambat terpropagasi), tangani secara otomatis tanpa menampilkan layar rusak kepada pengguna.

Nuxt 3 menyediakan hook runtime app:chunkError. Gabungkan hook ini dengan reloadNuxtApp() bawaan Nuxt, lengkap dengan proteksi pencegahan infinite reload loop:

// plugins/chunk-reload.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook("app:chunkError", ({ error }) => {
    const RELOAD_KEY = "nuxt_chunk_reload_lock";
    const MAX_RETRIES = 1;
    
    const retryCount = parseInt(sessionStorage.getItem(RELOAD_KEY) || "0", 10);

    if (retryCount < MAX_RETRIES) {
      sessionStorage.setItem(RELOAD_KEY, (retryCount + 1).toString());
      
      // Memaksa reload penuh dari server untuk mengambil manifest terbaru
      reloadNuxtApp({
        ttl: 10000, // Durasi lock reload (ms)
        force: true,
        persistState: false,
      });
    } else {
      // Hapus kunci agar sesi navigasi berikutnya dapat mencoba kembali jika diperlukan
      sessionStorage.removeItem(RELOAD_KEY);
      console.error("Batas reload otomatis terlampaui. Aset tidak ditemukan:", error);
    }
  });
});

Dengan menerapkan kombinasi retensi multi-versi di CDN dan hook penanganan reload otomatis di sisi klien, insiden ChunkLoadError selama rolling deployment dapat dieliminasi sepenuhnya tanpa mengorbankan pengalaman pengguna.