Pendahuluan

Hydration Stabil saat API CORS Gagal di SSR bukan sekadar soal error di console, melainkan konflik antara state server dan client ketika permintaan API ditolak karena CORS. Di bagian pembuka ini, langsung jelaskan bahwa solusi utama adalah memastikan response yang diterima dari server tetap konsisten sehingga HTML hasil SSR bisa di-hydrate tanpa mismatch, bahkan ketika API sebenarnya tidak bisa dipanggil karena policy CORS.

Pemahaman yang mendalam tentang CORS penting karena SSR/SSG akan mengeksekusi request sebelum halaman dikirimkan. Jika response berbeda antara request server dan permintaan ulang di browser (karena preflight ditolak, header hilang, atau body tiba-tiba kosong), hasilnya adalah perubahan DOM yang bikin hydration jadi tidak stabil. Konteks ini diperkuat dengan logika dari https://fosterelli.co/developers-dont-understand-cors: CORS bukan sekadar header tambahan, tapi batasan keamanan yang harus diterjemahkan dalam arsitektur agar render konsisten.

1. Akibat CORS Gagal terhadap Hydration

Ketika SSR/SSG mengandalkan data dari API eksternal, state yang dikirimkan ke client mengandalkan body JSON yang valid. Jika API tersebut menolak permintaan karena CORS (misalnya tidak ada Access-Control-Allow-Origin yang sesuai), server bisa menyuplai data fallback atau error placeholder, tetapi browser tidak akan mendapatkan payload sama sekali. Hasilnya DOM awal berbeda dengan DOM yang hendak di-hydrate, menyebabkan React/ framework lain mengeluarkan warning “Did not expect server HTML to contain a ... in

” dan komponen bisa pecah.

Untuk memastikan hydration stabil:

  • Pastikan respons SSR (server) selalu memiliki struktur data yang sama, baik data sesungguhnya maupun fallback.
  • Kirimkan state error yang eksplisit ke client agar UI bisa menampilkan pesan konsisten tanpa mengandalkan fetch client yang gagal.
  • Ketahui bahwa kegagalan CORS tidak bisa di-catch dari client dengan detail (browser blok), jadi solusi harus dimulai dari sisi server.

2. Diagnosa Network dan Hydration State

2.1 Analisis Network: Preflight dan Header

Gunakan toolbar DevTools dan fokus ke tab Network saat melakukan SSR (misalnya node server.js atau build). Cek dua hal:

  1. Preflight (OPTIONS): apakah request OPTIONS diterima dan dijawab dengan Access-Control-Allow-Origin, Access-Control-Allow-Methods, dan Access-Control-Allow-Headers yang diperlukan? Gagal di sini artinya header lanjutan seperti Authorization tidak diperbolehkan.
  2. GET/POST utama: apakah server merespons status 200 dengan JSON valid dan header CORS yang sama? Jika notifikasi error muncul (status 0, blocked), maka browser memang tidak meneruskan response ke client.

Ingat bahwa SSR langsung memanggil API tanpa mematuhi CORS (karena di server), sedangkan client akan mematuhi. Jadi mismatch terjadi ketika client berusaha me-refresh data yang sudah di-render oleh server.

2.2 Diagnosa Hydration State

Setelah response diterima oleh SSR, log state yang dikirim ke template/React sebelum dikirim ke HTML. Misalnya di Next.js:

export async function getServerSideProps() {
  try {
    const res = await fetch(process.env.API_URL)
    const payload = await res.json()
    return { props: { data: payload } }
  } catch (err) {
    return { props: { data: null, error: 'api_fetch_failed' } }
  }
}

Catat bahwa props selalu memiliki struktur { data, error }. Di client, logika komponen harus membaca prop tersebut untuk menghindari render yang berbeda:

export default function Page({ data, error }) {
  if (error) {
    return 
Data tidak tersedia saat ini.
} return }

Diagonal error ini mencegah React mencoba re-render berdasarkan fetch client yang gagal (karena CORS) dan membuat hydration tetap konsisten.

3. Strategi Fallback Data untuk Hydration Aman

Hydration yang stabil membutuhkan fallback data yang aman agar UI tetap logis saat client tidak bisa memanggil API. Strategi:

  • Gunakan cache sisi server: SSE atau layer cache (Redis, file) menyajikan data lama sebagai fallback ketika CORS menolak permintaan baru.
  • Tandai UI dengan state loading/error ekspisit: Jangan mengandalkan hook yang asinkron untuk menampilkan data yang mungkin tidak pernah datang.
  • Pastikan komponen menggunakan prop, bukan fetch ulang di client: Jika perlu memanggil ulang, lakukan fallback ke state sebelumnya sebelum melakukan fetch.

Contoh fallback:

const fallbackResponse = { items: [], hydratedAt: new Date().toISOString() }

export async function getStaticProps() {
  try {
    const res = await fetch('https://api.example.com/data')
    return { props: { data: await res.json() } }
  } catch (error) {
    return { props: { data: fallbackResponse, error: 'canceled_cors' } }
  }
}

Dengan cara ini, server tetap mengirim struktur yang sama sehingga client tidak akan melihat perbedaan content, dan kita bisa menampilkan pesan yang sesuai.

4. Konsistensi Header: Server & Proxy

Kelola respons CORS dari origin API yang sama dengan mekanisme yang konsisten:

  • Server asli harus menyertakan header Access-Control-Allow-Origin (tidak harus *, bisa origin spesifik) dan Access-Control-Allow-Credentials jika cookie digunakan.
  • Proxy internal (misalnya Nginx atau proxy middleware) bisa menyisipkan header yang sama agar API tetap bisa diakses dari browser tanpa memodifikasi API upstream.
  • Fallback level proxy: Saat request ke server lain, proxy bisa mengirim ulang request dengan header yang tepat lalu meneruskan respons ke browser dengan header CORS yang lengkap.

Contoh konfigurasi proxy sederhana di Node/Express:

app.use('/api', createProxyMiddleware({
  target: process.env.API_URL,
  changeOrigin: true,
  onProxyRes(proxyRes, req, res) {
    proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin || '*'
    proxyRes.headers['Access-Control-Allow-Credentials'] = 'true'
  }
}))

Pastikan header yang dikembalikan server SSR dan client sama persis, sehingga browser tidak memblokir response dan data tetap sesuai saat hydration.

5. Debugging dan Kesalahan Umum

Tips debugging:

  • Bandingkan header respons SSR dan header yang diterima client. Jika server sudah menyertakan CORS tapi browser tetap blok, artinya response difilter oleh proxy atau CDN.
  • Gunakan pesan error prop untuk menelusuri di console React apakah state error terjadi karena SSR atau fetch client.
  • Jangan menonaktifkan CORS di browser; itu hanya menutupi masalah dan membuat inconsistent production yang sulit ditangani.

Kesalahan umum termasuk: mengandalkan hook useEffect untuk fetch utama tanpa fallback, membiarkan struktur data berubah antara SSR dan client, dan tidak memastikan header CORS diterapkan di seluruh path (prefetch, preflight, main request).

Kesimpulan

Kunci menjaga hydration stabil saat API CORS gagal adalah memastikan state yang dikirimkan dari server tetap konsisten, menyediakan fallback yang eksplisit, serta menyesuaikan konfigurasi header di server dan proxy. Dengan memahami CORS bukan sebagai error semata tapi bagian dari flow keamanan, kita bisa menyusun pipeline render SSR/SSG yang tidak tergantung pada keberhasilan fetch di client.