Audit SDK pihak ketiga agar SSR dan hydration tetap stabil berarti memastikan kode vendor tidak mengubah HTML awal, state awal, atau perilaku render sebelum framework selesai melakukan hydration di browser. Jika langkah ini diabaikan, gejalanya biasanya berupa warning hydration mismatch, komponen yang berkedip, event handler tidak menempel, atau UI yang tampil berbeda antara server dan client.

Masalah ini makin relevan karena banyak aplikasi modern memuat analytics, chat widget, A/B testing, ads, feature flag, fraud detection, atau SDK monetisasi yang berjalan sangat awal. Dalam konteks audit SDK pada aplikasi Smart TV, diskusi tentang supply chain frontend mengingatkan bahwa risiko SDK bukan hanya keamanan dan privasi, tetapi juga stabilitas runtime. Di aplikasi SSR, satu SDK yang diam-diam menyentuh window, menyisipkan node DOM, atau mengubah state global bisa cukup untuk merusak hydration.

Mengapa SDK pihak ketiga sering merusak SSR dan hydration

Pada arsitektur SSR, server lebih dulu menghasilkan HTML awal. Setelah itu, browser memuat JavaScript dan framework melakukan hydration: menyambungkan tree komponen client ke markup yang sudah ada. Agar proses ini berhasil, output render di server dan client harus konsisten pada momen awal.

SDK pihak ketiga sering melanggar asumsi ini dengan beberapa cara:

  • Mengakses browser API saat import, misalnya membaca window, document, localStorage, atau ukuran viewport sebelum kode benar-benar berjalan di client.
  • Menyuntik DOM secara langsung, seperti menambah <script>, <iframe>, <div>, atau mengubah isi elemen root sebelum hydration selesai.
  • Mengubah state awal secara diam-diam, misalnya mengisi store global, menulis cookie, mengganti locale, atau mengubah flag eksperimen yang memengaruhi hasil render pertama.
  • Menghasilkan nilai non-deterministik, seperti timestamp, random ID, atau hasil deteksi environment yang berbeda antara server dan browser.
  • Menjalankan side effect saat module dievaluasi, bukan saat lifecycle yang aman di client.

Singkatnya: hydration mismatch hampir selalu muncul ketika SDK mengubah sesuatu yang seharusnya tetap identik antara HTML hasil SSR dan render awal di browser.

Gejala yang perlu dicurigai saat audit SDK pihak ketiga

Jangan hanya mencari error fatal. Banyak kasus muncul sebagai gangguan kecil tetapi konsisten:

  • Warning seperti Text content does not match atau Hydration failed.
  • Komponen tertentu berkedip dari tampilan A ke B setelah halaman terbuka.
  • Widget tampil dua kali karena server dan client masing-masing membuat node sendiri.
  • Event click, input, atau navigasi tidak bekerja pada area tertentu setelah hydration bermasalah.
  • Perbedaan HTML hanya terjadi pada environment tertentu, misalnya production, Smart TV browser, atau perangkat lambat.
  • Masalah hilang ketika SDK tertentu dimatikan.

Jika gejala hanya muncul setelah menambah package vendor, menyalakan eksperimen, atau memuat script eksternal lebih awal, audit SDK harus menjadi langkah pertama.

Root cause yang paling sering ditemukan

1. Side effect saat import module

Ini pola yang sangat umum. File SDK di-import di level atas, lalu kode vendor langsung mengeksekusi logika yang mengakses browser atau memodifikasi DOM.

// before: berisiko pada SSR/hydration
import thirdPartySdk from 'third-party-sdk'

thirdPartySdk.init({
  container: '#promo-slot'
})

export default function Page() {
  return <div id="promo-slot" />
}

Masalahnya, import seperti ini dapat dievaluasi terlalu dini. Jika SDK menyuntik elemen ke #promo-slot sebelum framework selesai hydration, markup client tidak lagi cocok dengan HTML dari server.

2. Render bergantung pada browser-only state

Contoh: SDK membaca cookie atau localStorage lalu mengubah state yang menentukan apakah banner, eksperimen, atau layout tertentu perlu ditampilkan pada render pertama.

3. Mutasi DOM di luar boundary framework

Jika SDK menulis langsung ke elemen yang juga dirender React, Vue, atau framework lain, maka framework kehilangan asumsi bahwa ia satu-satunya sumber kebenaran untuk subtree tersebut.

4. Ketidakkonsistenan state awal server dan client

Misalnya server merender pengguna sebagai anonim, tetapi saat startup client SDK identitas langsung mengubah store menjadi login atau segmented user sebelum hydration selesai. Hasilnya, tree awal berubah terlalu cepat.

Pola deteksi saat mengaudit SDK pihak ketiga

Inventaris dan klasifikasi SDK

Mulailah dari daftar sederhana:

  • Nama package atau script vendor
  • Di mana dimuat: layout global, halaman tertentu, atau komponen kecil
  • Kapan dijalankan: saat import, saat mount, setelah interaksi, atau setelah consent
  • Akses apa yang dilakukan: DOM, storage, cookie, network, global state
  • Apakah memengaruhi markup awal atau hanya telemetry

Klasifikasi ini membantu memisahkan SDK berisiko rendah dari SDK yang harus diisolasi.

Cari side effect pada level import

Audit file integrasi Anda sendiri lebih dulu. Banyak masalah bukan pada SDK murni, tetapi pada wrapper internal yang memanggil init() di top-level.

// anti-pattern
import sdk from 'vendor-sdk'
sdk.init()

export { sdk }

Pindahkan inisialisasi ke boundary client-only atau lifecycle yang aman.

Bandingkan HTML sebelum dan sesudah hydration

Gunakan DevTools untuk memeriksa apakah ada node tambahan, atribut berubah, atau teks berbeda sebelum interaksi pengguna. Jika subtree tertentu berubah tepat setelah script vendor dimuat, itu petunjuk kuat.

Matikan SDK satu per satu

Pendekatan paling praktis sering kali adalah binary isolation: nonaktifkan SDK secara bertahap sampai mismatch hilang. Jika aplikasi memiliki banyak vendor script, cara ini lebih cepat daripada menebak.

Tambahkan logging pada fase bootstrap

Catat kapan SDK di-load, kapan init dipanggil, dan kapan DOM target berubah. Timestamp sederhana sering cukup untuk melihat bahwa perubahan terjadi sebelum hydration selesai.

Strategi utama: isolasi SDK di client-only boundary

Prinsip dasarnya: jangan biarkan SDK vendor ikut menentukan markup SSR kecuali Anda benar-benar yakin perilakunya deterministik dan aman untuk server. Untuk mayoritas SDK UI, analytics, chat widget, ads, atau eksperimen, pilihan terbaik adalah memuatnya hanya di client.

Isolasi komponen yang bergantung pada SDK

Buat boundary yang jelas: server merender placeholder yang stabil, lalu client memuat SDK setelah mount.

// after: pola aman secara umum
import { useEffect, useRef } from 'react'

export default function PromoWidget() {
  const containerRef = useRef(null)

  useEffect(() => {
    let disposed = false

    async function load() {
      const { default: sdk } = await import('third-party-sdk')
      if (disposed || !containerRef.current) return

      sdk.init({
        container: containerRef.current
      })
    }

    load()
    return () => {
      disposed = true
    }
  }, [])

  return <div ref={containerRef} data-sdk-slot="promo" />
}

Mengapa pola ini lebih aman:

  • SDK tidak dievaluasi saat SSR.
  • DOM target sudah ada dan dikelola secara eksplisit.
  • Side effect ditunda sampai komponen benar-benar ter-mount di browser.
  • Anda bisa menambahkan cleanup, retry, atau observability dengan mudah.

Pisahkan subtree milik framework dan subtree milik SDK

Jika SDK harus memanipulasi DOM, sediakan satu container khusus dan anggap area itu milik vendor. Jangan campur node yang sama untuk dirender framework dan dimutasi SDK.

Aturan praktis: satu elemen root untuk satu pemilik. Jika React/Vue merender subtree, jangan biarkan SDK vendor juga menulis ke subtree yang sama.

Dynamic import, guard browser API, dan penundaan side effect

Gunakan dynamic import untuk menunda evaluasi module

import() membantu karena module baru dievaluasi saat benar-benar dibutuhkan di browser. Ini sering lebih aman daripada import statis untuk SDK yang tidak SSR-friendly.

Trade-off-nya:

  • Ada latensi tambahan saat SDK baru dimuat setelah halaman tampil.
  • Anda perlu menangani loading state atau fallback.
  • Tidak semua masalah selesai jika SDK tetap memodifikasi area DOM yang salah.

Guard window dan document

Jika Anda menulis wrapper integrasi sendiri, jangan akses browser API tanpa pengecekan environment.

export function initVendorSdk() {
  if (typeof window === 'undefined' || typeof document === 'undefined') {
    return
  }

  // logic browser-only
}

Guard ini mencegah crash saat SSR, tetapi tidak otomatis mencegah hydration mismatch. Jika sesudah guard SDK masih mengubah DOM terlalu cepat, mismatch tetap bisa terjadi. Jadi guard hanyalah lapisan pertama.

Tunda side effect sampai mount atau idle

Untuk SDK yang tidak kritis bagi tampilan awal, jalankan setelah mount, setelah interaksi, atau saat browser sedang lebih senggang. Tujuannya bukan sekadar performa, tetapi juga memberi kesempatan hydration selesai lebih dulu.

Pilihan umum:

  • Lifecycle mount seperti useEffect atau hook setara
  • Trigger setelah consent
  • Lazy load saat elemen masuk viewport
  • Jalankan setelah interaksi pengguna

Pilih pendekatan ini untuk analytics, chat, heatmap, atau widget promosi yang tidak perlu memengaruhi render awal.

Validasi state awal agar server dan client konsisten

Banyak mismatch bukan karena DOM injection, tetapi karena state awal berubah sebelum hydration selesai. Karena itu, audit juga harus memeriksa bagaimana SDK berinteraksi dengan store, cookie, dan feature flag.

Prinsip yang aman

  • Server menentukan initial state untuk hal-hal yang memengaruhi markup awal.
  • Client boleh memperkaya state setelah hydration, tetapi perubahan itu harus dianggap update normal, bukan bagian dari render awal.
  • Jangan baca localStorage/cookie secara diam-diam di level import lalu langsung ubah state global.

Contoh before/after state initialization

// before: SDK mengubah state terlalu dini
import sdk from 'vendor-sdk'
import { store } from './store'

const segment = sdk.getSegmentFromBrowser()
store.setSegment(segment)
// after: state awal tetap stabil, update dilakukan di client setelah mount
import { useEffect } from 'react'
import { useStore } from './store'

export function SegmentBootstrap() {
  const setSegment = useStore((s) => s.setSegment)

  useEffect(() => {
    let active = true

    async function run() {
      const { default: sdk } = await import('vendor-sdk')
      const segment = sdk.getSegmentFromBrowser?.()
      if (active && segment) setSegment(segment)
    }

    run()
    return () => {
      active = false
    }
  }, [setSegment])

  return null
}

Dengan pola ini, HTML awal tidak bergantung pada hasil baca browser-only state yang hanya tersedia di client.

Panduan praktis untuk Next.js, Nuxt, dan Inertia

Next.js

  • Tempatkan SDK UI atau browser-only di komponen client, bukan pada file yang ikut dieksekusi saat SSR.
  • Gunakan dynamic import untuk komponen atau module yang tidak aman untuk server.
  • Jika vendor harus memanipulasi DOM, render container stabil lalu inisialisasi di effect setelah mount.
  • Hindari membaca window, document, atau storage pada top-level module.

Nuxt

  • Pastikan plugin atau integrasi vendor dibatasi ke client-only ketika memang tidak SSR-safe.
  • Jangan biarkan plugin vendor mengubah state yang memengaruhi markup awal tanpa sinkronisasi dari server.
  • Untuk widget DOM-heavy, lebih aman gunakan boundary client-only dan container khusus.

Inertia

  • Walau model render-nya berbeda dari SSR penuh pada beberapa setup, masalah UI membingungkan tetap bisa muncul ketika SDK memodifikasi DOM atau state terlalu cepat.
  • Load SDK setelah komponen halaman mount, terutama untuk widget eksternal, analytics, dan eksperimen berbasis browser.
  • Jaga agar props awal dari server tetap menjadi sumber kebenaran pertama sebelum SDK melakukan enrichment di client.

Perbedaan framework memang ada, tetapi aturan umumnya sama: batasi evaluasi SDK ke sisi client, tunda side effect, dan jangan campurkan kepemilikan DOM.

Checklist debugging hydration mismatch akibat SDK

  1. Reproduksi tanpa cache. Pastikan script lama, service worker, atau cached HTML tidak menipu hasil.
  2. Matikan semua SDK non-esensial, lalu aktifkan satu per satu.
  3. Cari import statis vendor pada layout, app shell, plugin global, dan file store.
  4. Periksa akses browser API di top-level: window, document, navigator, localStorage, cookie helper.
  5. Bandingkan HTML server vs DOM setelah load pada area yang bermasalah.
  6. Pastikan SDK hanya menulis ke container khusus, bukan ke node yang juga dirender framework.
  7. Audit perubahan state awal: locale, theme, auth hint, experiment variant, segment, consent state.
  8. Tunda init SDK ke mount, idle, viewport, atau setelah consent untuk melihat apakah mismatch hilang.
  9. Tambahkan logging bootstrap untuk urutan: render server, mount client, init SDK, DOM mutation, store mutation.
  10. Uji pada perangkat/browser target, termasuk environment terbatas seperti Smart TV browser, karena perilaku timing dan API support bisa berbeda.

Kesalahan umum yang sering terlewat

  • Menganggap guard typeof window sudah cukup. Guard mencegah crash server, bukan semua mismatch.
  • Meletakkan script vendor di layout global padahal hanya dipakai di satu halaman.
  • Membiarkan SDK mengatur variasi UI awal tanpa state awal yang konsisten dari server.
  • Mencampur analytics ringan dan widget DOM-heavy dalam strategi yang sama. Keduanya punya risiko berbeda.
  • Tidak mendokumentasikan ownership atas elemen DOM tertentu, sehingga tim lain tanpa sadar merender ulang area yang sudah dimodifikasi SDK.

Kapan SDK boleh ikut SSR, dan kapan harus client-only

Boleh dipertimbangkan untuk ikut SSR jika SDK atau wrapper Anda benar-benar murni, tidak menyentuh browser API saat render, tidak memodifikasi DOM di luar framework, dan hasilnya deterministik antara server dan client.

Lebih aman client-only jika SDK:

  • bergantung pada browser API,
  • menyisipkan script/iframe/node,
  • mengubah state global saat startup,
  • berperan sebagai widget pihak ketiga, atau
  • sulit diaudit karena kode vendor tertutup.

Dalam praktiknya, mayoritas SDK pemasaran, monetisasi, eksperimen, dan widget eksternal lebih cocok diperlakukan sebagai client-only dependency.

Penutup

Untuk menjaga SSR dan hydration tetap stabil, audit SDK pihak ketiga harus fokus pada tiga pertanyaan: kapan SDK dievaluasi, apa yang diubah sebelum hydration selesai, dan siapa pemilik DOM atau state awal. Jika jawabannya tidak jelas, isolasi di client-only boundary adalah pilihan paling aman.

Pendekatan yang biasanya berhasil adalah: inventaris SDK, cari side effect saat import, gunakan dynamic import, guard browser API, tunda side effect sampai mount, validasi agar initial state server dan client konsisten, lalu debug dengan mematikan vendor satu per satu. Dengan disiplin ini, Anda bukan hanya mengurangi warning hydration mismatch, tetapi juga mencegah UI membingungkan yang sulit direproduksi di production.