LLM di SSR UI sering gagal bukan karena modelnya salah, tetapi karena markup hasil render server tidak identik dengan yang diharapkan saat hydrasi di browser. Begitu output model bersifat non-deterministik, dipersonalisasi per request, atau datang lewat streaming parsial, React, Vue, atau Svelte dapat mendeteksi perbedaan DOM dan memunculkan hydration mismatch, UI berkedip, atau event handler terpasang pada node yang salah.

Solusi praktisnya sederhana: jangan biarkan hasil LLM menentukan markup kritis yang harus identik antara server dan client. Gunakan LLM terutama di backend untuk penyusunan konten, klasifikasi, tagging, enrichment metadata, atau rekomendasi yang disimpan sebagai data stabil. Jika hasil model perlu tampil di UI SSR, perlakukan ia sebagai data yang sudah dibekukan untuk satu request, bukan sesuatu yang dihitung ulang saat client hydrate.

Catatan konteks: pendekatan ini sejalan dengan prinsip use-case LLM yang efektif di backend—membantu klasifikasi, ekstraksi, peringkasan, atau enrichment—tanpa menaruh model di jalur render UI yang membutuhkan determinisme ketat.

Kenapa LLM Mudah Memicu Hydration Drift

Hydration mengasumsikan HTML yang dikirim server sama dengan hasil render awal di client. SSR framework boleh berbeda implementasi, tetapi prinsipnya sama: browser menerima HTML statis, lalu runtime client memasang event handler dan merekonsiliasi state di atas struktur DOM yang sudah ada.

Masalah muncul ketika sebagian UI bergantung pada LLM dan input render-nya tidak identik antara server dan client. Beberapa pemicu umum:

  • Output non-deterministik: prompt sama tidak selalu menghasilkan teks identik.
  • State awal berbeda: server merender dengan hasil model A, client menghitung ulang dan mendapat hasil model B.
  • Personalisasi per request: cookie, header, locale, A/B bucket, atau tenancy memengaruhi prompt dan output.
  • Streaming parsial: server mengirim sebagian konten, client hydrate saat data belum final.
  • Normalisasi yang tidak konsisten: trimming, markdown rendering, sanitasi HTML, atau parser berbeda antara server dan client.

Gejala yang Biasanya Terlihat

  • Peringatan hydration mismatch di console.
  • Flash konten: teks berubah segera setelah JS aktif.
  • Komponen interaktif kehilangan state atau event handler tidak bekerja seperti yang diharapkan.
  • Daftar item berubah urutan atau jumlah setelah hydrate.
  • Bug yang hanya muncul pada user tertentu karena personalisasi request.

Akar Masalah Teknis: Bukan Sekadar “LLM Acak”

1. Output model ikut membentuk markup kritis

Hydration paling rapuh ketika hasil model menentukan elemen struktural: apakah blok tertentu dirender, jumlah item dalam list, heading mana yang muncul, atribut penting, atau urutan node. Selama LLM hanya mengisi teks dalam wadah yang stabil, risikonya lebih kecil. Begitu ia menentukan struktur DOM, sedikit perbedaan bisa memicu drift.

2. Server dan client tidak membaca sumber data yang sama

Pola yang sering salah: server memanggil LLM saat SSR, lalu client juga memanggil LLM atau pipeline turunan saat mount untuk “menyegarkan” konten. Ini membuat dua sumber kebenaran pada fase render awal. Hydration membutuhkan satu snapshot data yang identik, bukan dua evaluasi terpisah terhadap prompt yang sama.

3. Personalisasi tersembunyi di request

Header seperti Accept-Language, cookie eksperimen, region, atau role user bisa memengaruhi prompt dan hasil model. Jika server memakai semua konteks ini, tetapi client tidak menyertakan konteks yang sama saat render awal, output akan bergeser. Bahkan beda timezone atau formatting tanggal yang disisipkan ke prompt dapat mengubah hasil.

4. Streaming SSR dan streaming model bercampur

Streaming HTML dan streaming token adalah dua mekanisme yang berbeda. Mencampur keduanya tanpa batasan bisa berbahaya. Jika server sudah mengirim shell HTML lalu hasil model masih berubah selama hydrate dimulai, client dapat melihat representasi perantara yang tidak sama dengan final DOM.

5. Renderer markdown/HTML tidak konsisten

Kasus umum: server mengubah output LLM ke HTML memakai satu parser, client memakai parser lain atau versi konfigurasi berbeda. Perbedaan kecil seperti wrapping paragraf, daftar kosong, escaping karakter, atau auto-linking cukup untuk mengubah struktur node.

Use-Case LLM yang Aman untuk Arsitektur SSR

Bukan berarti LLM tidak cocok untuk produk berbasis SSR. Justru ia efektif jika ditempatkan pada jalur yang tepat.

Gunakan LLM di backend untuk data, bukan struktur render awal

Pola aman yang disarankan:

  • Penyusunan konten: menghasilkan draft, ringkasan, subtitle, atau alternatif copy yang disimpan lalu direview atau dibekukan sebelum ditampilkan.
  • Klasifikasi: intent, kategori, sentiment, prioritas tiket, topik artikel, label moderation.
  • Enrichment: keyword, metadata SEO, tag, entitas, FAQ turunan, related content candidates.
  • Ekstraksi terstruktur: ubah teks bebas menjadi JSON tervalidasi lalu pakai JSON itu sebagai data render.

Kuncinya: hasil model menjadi artefak data yang stabil. Server dan client sama-sama membaca snapshot yang sama untuk satu request atau satu versi konten.

Batasi LLM dari area berikut

  • Menentukan apakah komponen SSR inti muncul atau tidak.
  • Menghasilkan HTML mentah yang langsung menjadi struktur halaman utama.
  • Memilih jumlah item, urutan, atau key list saat render awal client.
  • Mengubah copy kritis secara real-time selama hydrate berlangsung.

Pola Aman untuk Mencegah Hydration Drift

1. Freeze hasil LLM per request

Jika hasil LLM harus tampil pada SSR, hitung sekali di server, simpan hasil final ke payload halaman, lalu client hydrate memakai payload itu tanpa memanggil ulang model. Anggap hasil tersebut immutable sampai interaksi eksplisit berikutnya.

// Contoh pola umum: server menghasilkan snapshot final untuk satu request
const llmResult = await generateSummary(input)
const pageData = {
  articleId: article.id,
  summary: llmResult.summary,
  summaryVersion: llmResult.version || 'req-snapshot'
}

return renderPage({ pageData })

Di client, gunakan pageData.summary sebagai sumber render awal. Jika ingin refresh setelah mount, tampilkan sebagai update terpisah yang tidak mengganggu struktur SSR.

2. Render placeholder stabil, isi hasil LLM setelah mount

Untuk konten yang benar-benar dinamis, lebih aman kirim placeholder SSR yang deterministik, lalu muat hasil LLM sebagai enhancement client-side. Pendekatan ini mengorbankan sebagian manfaat SSR pada area tersebut, tetapi menghilangkan mismatch di markup kritis.

<section aria-busy="true" data-llm-slot>
  <p>Menyiapkan ringkasan...</p>
</section>

Pastikan placeholder server dan render awal client identik. Setelah mount, baru ganti isi kontainer tersebut.

3. Pisahkan struktur dan konten

Gunakan SSR untuk membangun kerangka DOM yang stabil: heading, section, wrapper, urutan komponen, dan elemen interaktif utama. Biarkan LLM hanya mengisi field teks atau metadata non-struktural.

Contoh aman:

  • Judul section, tombol, layout kartu, dan jumlah item ditentukan aplikasi.
  • LLM hanya mengisi subtitle, excerpt, atau badge text yang sudah dibatasi panjang dan formatnya.

4. Konversi output model ke skema terstruktur

Daripada langsung merender teks bebas atau HTML dari model, ubah hasil ke JSON tervalidasi. Ini membantu Anda menjaga batasan bentuk data dan mengurangi variasi struktur.

{
  "label": "database",
  "summary": "Artikel membahas konsistensi render antara server dan client.",
  "confidence": 0.88
}

Setelah tervalidasi, aplikasi yang menentukan bagaimana JSON itu dirender. Bukan model.

5. Cache berdasarkan input yang benar-benar relevan

Jika output dipersonalisasi, definisikan kunci cache secara eksplisit: misalnya contentId + locale + tenant + experimentBucket. Tujuannya bukan sekadar performa, tetapi juga konsistensi antara SSR dan navigasi client berikutnya.

Namun, jangan memakai cache untuk menutupi desain yang salah. Jika client tetap menghitung ulang dengan konteks berbeda, cache tidak akan menyelesaikan akar masalah.

6. Gunakan progressive enhancement untuk area berisiko

Untuk fitur seperti “ringkasan AI”, “FAQ hasil AI”, atau “saran tindak lanjut”, anggap itu enhancement sekunder. Halaman inti tetap dapat digunakan tanpa fitur ini. Jika pipeline model lambat, timeout, atau berbeda hasil, pengalaman utama tidak rusak.

Anti-Pattern yang Sering Terjadi

Memanggil LLM saat render di server dan di client

Ini anti-pattern paling jelas. Prompt sama tidak menjamin hasil sama. Bahkan jika model tampak stabil di pengujian lokal, drift akan muncul pada beban nyata, perubahan konteks, atau deploy berikutnya.

Merender HTML mentah dari model ke DOM SSR

Selain risiko keamanan bila sanitasi tidak ketat, Anda juga membuka peluang mismatch dari parser markdown/HTML yang berbeda antara lingkungan server dan browser.

Membiarkan model menentukan list key atau urutan elemen

Jika LLM memilih item rekomendasi saat SSR dan client menyusun ulang saat mount, hydration akan rapuh. Key stability penting untuk semua framework utama.

Mencampur personalisasi request ke prompt tanpa serialisasi state

Server mungkin mengetahui locale, plan user, dan eksperimen aktif. Jika informasi ini tidak diserialisasi ke payload yang sama untuk client, render awal client tidak dapat mereproduksi keputusan yang sama.

Streaming token langsung ke markup yang sudah akan di-hydrate

Streaming cocok untuk chat atau panel yang jelas sepenuhnya client-managed. Untuk area SSR yang akan di-hydrate, streaming token yang mengubah struktur saat hydrate berlangsung adalah sumber bug yang mahal dilacak.

Strategi Fallback yang Praktis

Fallback ke konten deterministik

Jika model gagal, timeout, atau confidence rendah, tampilkan konten non-AI yang stabil: excerpt editorial, metadata yang sudah ada, atau template default. Ini lebih baik daripada memaksa hasil model masuk ke SSR.

Fallback ke client-only island

Jika sebuah widget AI memang sangat dinamis, render sebagai island atau komponen client-only. Shell SSR tetap stabil, sementara widget AI hidup terpisah setelah mount.

Fallback ke data versi terakhir yang tervalidasi

Untuk enrichment seperti tag atau ringkasan, gunakan versi terakhir yang tersimpan jika proses baru belum selesai. Pola ini umum untuk halaman konten yang sering dibuka dan tidak perlu benar-benar real-time.

Timeout pendek di jalur request

Jangan biarkan request SSR menunggu model terlalu lama. Tetapkan batas waktu ketat, lalu jatuh ke fallback yang deterministik. Tujuannya menjaga latensi dan mencegah halaman berada pada state setengah jadi.

Logging dan Debugging: Bandingkan SSR vs CSR Secara Eksplisit

Hydration drift sulit diperbaiki jika Anda hanya mengandalkan pesan console generik. Yang dibutuhkan adalah visibilitas terhadap snapshot input render di server dan client.

Apa yang perlu dicatat

  • Request ID yang sama untuk server dan client.
  • Prompt fingerprint atau hash input, bukan prompt mentah jika sensitif.
  • Model output fingerprint atau hash hasil final.
  • Context fingerprint: locale, tenant, experiment bucket, user segment anonim.
  • Version renderer: parser markdown/sanitizer/template version.
  • SSR payload checksum yang diserialisasi ke halaman.

Contoh payload diagnostik

{
  "requestId": "req_7f2c",
  "route": "/articles/123",
  "llm": {
    "inputHash": "a91d...",
    "outputHash": "c44b...",
    "contextHash": "f12e..."
  },
  "render": {
    "payloadChecksum": "9ab1...",
    "markdownRenderer": "stable-config"
  }
}

Sisipkan checksum ini ke HTML sebagai data attribute atau script payload non-sensitif. Saat client mount, log checksum yang dipakai untuk render awal. Jika berbeda dari server, Anda punya titik awal investigasi yang jelas.

Teknik diff yang berguna

  • Bandingkan payload JSON SSR dengan state awal di client sebelum komponen interaktif berjalan.
  • Log panjang string, hash, dan field penting, bukan seluruh konten jika sensitif atau besar.
  • Jika memakai markdown, bandingkan AST atau HTML hasil sanitasi, bukan hanya teks mentah.
  • Reproduksi dengan mematikan cache dan menempelkan request headers yang sama.

Panduan Implementasi per Framework

Next.js

  • Jangan panggil model langsung dari komponen client untuk menghasilkan ulang konten yang sudah dirender server.
  • Jika memakai Server Components atau data fetching di server, serialisasikan hasil final sebagai props/payload yang sama untuk render awal client.
  • Untuk widget AI yang dinamis, pertimbangkan komponen client-only atau placeholder stabil di SSR.
  • Pastikan parser markdown/sanitasi berada di sisi server saja, atau gunakan konfigurasi identik bila memang harus dipakai di dua sisi.
  • Jika ada personalisasi, serialisasikan semua konteks yang memengaruhi output ke payload awal, bukan hanya mengandalkan cookie saat request server.

Nuxt

  • Ambil hasil enrichment LLM di server dan simpan ke state yang di-hydrate ke client, jangan hitung ulang saat mount.
  • Gunakan komponen client-only untuk panel AI yang sangat interaktif atau streaming.
  • Waspadai perbedaan transformasi data antara plugin server dan plugin client.
  • Untuk route yang dipersonalisasi, pastikan kunci cache dan payload state memasukkan faktor seperti locale atau segment user.

SvelteKit

  • Lakukan pekerjaan LLM di load server-side atau backend terpisah, lalu kirim hasil tervalidasi ke halaman.
  • Hindari membuat hasil model ulang di browser selama render awal.
  • Untuk fitur AI sekunder, gunakan enhancement setelah mount dengan fallback HTML yang stabil.
  • Jaga agar data yang dikirim dari server menjadi satu-satunya sumber kebenaran untuk fase hydrasi.

Contoh Arsitektur yang Aman

Pola rekomendasi: LLM sebagai pipeline backend, bukan engine render

  1. Konten utama atau data bisnis masuk ke backend.
  2. Job backend memanggil LLM untuk klasifikasi/ringkasan/enrichment.
  3. Hasil model divalidasi, dinormalisasi, dan disimpan sebagai record terstruktur.
  4. SSR membaca record terstruktur tersebut sebagai data biasa.
  5. Client hydrate memakai payload yang sama persis.
  6. Jika butuh pembaruan AI, lakukan sebagai update setelah mount atau via refresh eksplisit.

Pola ini memberi tiga keuntungan: determinisme lebih baik, observabilitas lebih baik, dan blast radius lebih kecil saat model berubah perilaku.

Checklist Implementasi

  • Apakah output LLM memengaruhi struktur DOM awal? Jika ya, ubah desainnya.
  • Apakah server dan client memakai snapshot data yang identik saat hydrasi?
  • Apakah hasil model dibekukan per request atau per versi konten?
  • Apakah personalisasi request diserialisasi ke state awal secara eksplisit?
  • Apakah ada parser markdown/HTML/sanitizer berbeda antara server dan client?
  • Apakah widget AI yang sangat dinamis dipindah ke client-only atau island?
  • Apakah ada timeout dan fallback deterministik saat model lambat atau gagal?
  • Apakah request ID, payload checksum, dan hash output tercatat untuk diff SSR/CSR?
  • Apakah list key, urutan item, dan jumlah elemen tidak ditentukan ulang oleh model saat mount?
  • Apakah hasil LLM diperlakukan sebagai data tervalidasi, bukan HTML mentah yang dipercaya begitu saja?

Kesimpulan

LLM di SSR UI menjadi masalah ketika model dimasukkan ke jalur render yang menuntut determinisme. Hydration drift biasanya berasal dari dua evaluasi yang berbeda terhadap data atau konteks yang tampak sama, padahal tidak benar-benar identik.

Pendekatan yang paling aman adalah memakai LLM untuk pekerjaan backend yang efektif—penyusunan konten, klasifikasi, dan enrichment—lalu mengirim hasil final yang stabil ke UI. Jika fitur AI harus sangat dinamis, pisahkan dari markup kritis SSR dengan placeholder stabil, client-only widget, atau progressive enhancement. Dengan begitu, Anda tetap mendapat manfaat LLM tanpa merusak kontrak dasar antara server render dan client hydration.