pertanyaan dijawab, penjualan selamatchat-nya nggak butuh banner cookieanggota tim tanpa batasbalas dari slack atau app kami

/blog / panduan

tambahkan live chat ke situs next.js

· oleh kuba · 5 menit baca

Steker berlabel next.js yang hampir masuk ke stopkontak pink, dengan kucing Spookat menunggangi kabelnya.

Live chat di Next.js cukup satu tag script di root layout. Panduan ini menunjukkan di mana tag itu ditaruh di app router dan pages router, cara mengirim tampilan widget dan pengguna yang sedang login tanpa merusak urutan muat, apa yang perlu ditambahkan ke Content Security Policy, dan cara mengecek hasilnya di build produksi.

versi singkatnya

Di app router, tambahkan tag ke app/layout.tsx, di dalam <body>:

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <script src="https://cdn.spookat.com/s/YOUR_SITE_KEY.js" async></script>
      </body>
    </html>
  );
}

Ganti YOUR_SITE_KEY dengan key dari halaman instalasi di dashboard. Itu seluruh instalasinya kalau tampilan diatur di dashboard, dan memang itu default-nya. Sisa panduan ini untuk kalau kamu ingin kontrol lebih. Kalau mulai dari nol, panduan utama membahas sisi akunnya.

kenapa root layout

Root layout membungkus setiap route dan tetap terpasang selama pengguna berpindah halaman. Launcher menempel ke halaman sekali saja. Navigasi di sisi klien tidak me-reload halaman, jadi launcher juga tidak di-reload, dan pengunjung bisa tetap membuka percakapan sambil mengklik ke mana-mana.

Kalau tag ditaruh di komponen halaman, chat hanya ada di halaman itu. Kalau ditaruh di komponen yang dirender di beberapa halaman, kamu akan dapat duplikat saat hard reload (launcher mengabaikan salinan keduanya sendiri, tapi tetap saja itu request yang terbuang).

dengan next/script

next/script adalah cara Next.js memuat script pihak ketiga, dan di sini berfungsi baik:

import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script src="https://cdn.spookat.com/s/YOUR_SITE_KEY.js" strategy="afterInteractive" />
      </body>
    </html>
  );
}

afterInteractive memuat launcher tepat setelah hydration. lazyOnload menunggu sampai browser idle, sehingga tombolnya muncul sedikit lebih lambat di HP yang lambat. Keduanya oke untuk launcher 2 kb; afterInteractive menampilkan tombol lebih cepat. Jangan pakai beforeInteractive untuk launcher: ia akan bersaing dengan kodemu sendiri tanpa guna.

mengatur tampilan lewat kode

Tampilan yang kamu publish di dashboard terbawa di dalam launcher. Kalau kamu lebih suka menyimpannya di repo, set window.Spookat sebelum launcher berjalan. Config di halaman mengalahkan config dashboard.

Urutan muat itu penting: config harus sudah ada saat launcher dijalankan. Dengan next/script, beri config beforeInteractive (diizinkan di root layout) dan launcher afterInteractive:

import Script from "next/script";

const spookat = { style: "clean", font: "system", accent: "#2F6FEB", side: "right" };

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script id="spookat-config" strategy="beforeInteractive">
          {`window.Spookat = ${JSON.stringify(spookat)};`}
        </Script>
        <Script src="https://cdn.spookat.com/s/YOUR_SITE_KEY.js" strategy="afterInteractive" />
      </body>
    </html>
  );
}

Script beforeInteractive berjalan sebelum hydration, afterInteractive sesudahnya, jadi urutannya terjamin. Key-nya sama seperti di tempat lain: style (salah satu dari 8), font, accent, side, greeting, label, lang. Dokumentasi mendaftar semuanya.

pages router

Di pages router, wrapper bersamanya adalah pages/_app.tsx:

import type { AppProps } from "next/app";
import Script from "next/script";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Component {...pageProps} />
      <Script src="https://cdn.spookat.com/s/YOUR_SITE_KEY.js" strategy="afterInteractive" />
    </>
  );
}

Untuk config, di pages router script beforeInteractive ditaruh di pages/_document.tsx. Taruh script window.Spookat di sana, di dalam <Head>, dengan id dan strategy yang sama seperti di atas, dan biarkan launcher di _app.tsx.

pengguna yang sedang login

Mulai pro, kamu bisa memberi tahu chat siapa yang sedang login. Timmu lalu melihat logged in as 42 di kartu Slack, dan webhook, ekspor, serta read_convo milik agenmu membawa id-nya. Id itu ditandatangani dengan secret yang hanya diketahui servermu, jadi tidak ada yang bisa menyamar sebagai orang lain.

Buat identity secret di halaman instalasi dashboard, simpan di environment variable, lalu tandatangani di server. Di app router, root layout adalah server component, jadi ini mudah:

import { createHmac } from "node:crypto";
import Script from "next/script";
import { getSession } from "@/lib/auth"; // yours

const sign = (id: string) => createHmac("sha256", process.env.SPOOKAT_IDENTITY_SECRET!).update(id).digest("hex");

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const session = await getSession();
  const user = session ? { id: String(session.userId), sig: sign(String(session.userId)) } : undefined;
  return (
    <html lang="en">
      <body>
        {children}
        {user && (
          <Script id="spookat-config" strategy="beforeInteractive">
            {`window.Spookat = ${JSON.stringify({ user })};`}
          </Script>
        )}
        <Script src="https://cdn.spookat.com/s/YOUR_SITE_KEY.js" strategy="afterInteractive" />
      </body>
    </html>
  );
}

Jangan pernah menghitung tanda tangan di client component: secret-nya akan ikut terkirim ke browser. Tanda tangan yang hilang atau salah bukan error; chat hanya tidak menyebutkan siapa penggunanya. Membaca session di root layout membuat setiap halaman dirender per request, jadi kalau layout-mu sekarang statis, pertimbangkan dulu sebelum menambahkannya.

content security policy

Kalau aplikasimu mengirim header Content-Security-Policy, widget butuh:

  • script-src dan style-src: https://cdn.spookat.com
  • font-src: https://cdn.spookat.com, kalau kamu memilih font yang di-host
  • connect-src: wss://api.spookat.com, untuk koneksi chat setelah klik
  • img-src: data:, untuk logo dan foto rekan tim
  • style-src: launcher menyisipkan satu blok <style> kecil ke shadow root-nya sendiri, jadi inline style harus diizinkan untuknya

Tanpa ini, console browser akan menyebut persis direktif mana yang memblokir apa. Tambahkan sumber yang disebut, lalu reload.

cek di build produksi

Mode dev menambahkan script-nya sendiri dan hot reload, jadi cek build yang sungguhan:

next build && next start

Buka http://localhost:3000 di jendela private dengan DevTools terbuka. Domain itu penting: chat hanya terhubung dari domain yang kamu daftarkan dan subdomainnya, jadi di localhost kamu akan melihat launcher, tapi chat tidak terhubung. Tes chat-nya sendiri di situs yang sudah di-deploy.

Di keduanya:

  1. Network, “Disable cache”, reload, filter spookat: satu request, launcher dari cdn.spookat.com. Dua, keduanya dari cdn.spookat.com, kalau kodemu mengatur style di window.Spookat yang bukan style yang kamu publikasikan. Tidak ada ke api.spookat.com.
  2. Application: tidak ada cookie dari Spookat, dan tidak ada penyimpanan sampai pesan pertama.
  3. Performance atau Lighthouse: launcher tidak boleh muncul sebagai long task. Bandingkan beberapa run dengan dan tanpa launcher; selisihnya seharusnya masih di dalam noise normal antar-run.
  4. Klik launcher di situs yang sudah di-deploy: panel dimuat, koneksi terbuka, pesanmu sampai ke Slack.

target deploy

Tidak ada yang bergantung pada di mana aplikasi berjalan. Vercel, server Node, container, atau output: "export" untuk situs statis semuanya menyajikan HTML yang sama, dan tag-nya berfungsi sama di mana pun. Untuk export statis, pengguna yang login harus datang dari server yang kamu kendalikan, karena tidak ada server render untuk menandatanganinya.

hubungkan balasannya

Setelah chat ada di situs, hubungkan Slack di dashboard dan pilih channel. Panduan Slack memandumu. Kalau kamu pakai Claude Code, panduan Claude Code menunjukkan cara membiarkan agent mengerjakan semua hal di atas.

aplikasi multibahasa

Kalau aplikasimu punya segmen [locale], beri tahu widget bahasa halamannya, supaya panel mengikuti halaman, bukan browser pengunjung. Widget ini bisa 12 bahasa; lang menerima kode seperti en, pl, de. Di app/[locale]/layout.tsx:

<Script id="spookat-config" strategy="beforeInteractive">
  {`window.Spookat = ${JSON.stringify({ lang: locale })};`}
</Script>

Bahasa yang tidak dimiliki widget akan jatuh ke bahasa browser pengunjung, lalu ke bahasa Inggris. Timmu tetap melihat bahasanya di chat.

kesalahan umum

  • Tag di dalam client component dengan "use client". Tetap dirender, tapi ikut di-hydrate bersama komponennya, dan setiap remount jadi peluang baru untuk duplikat. Simpan di layout.
  • Membuat script di useEffect. Lebih banyak kode untuk hasil yang sama, dan dimuat lebih lambat daripada afterInteractive.
  • type="module" di tag. Launcher adalah script klasik; jangan pakai type.
  • Preload panel. <link rel="preload"> untuk script panel memindahkan unduhannya ke sebelum klik untuk setiap pengunjung. Intinya justru panel itu menunggu.
  • Tes chat di deployment preview. URL preview itu domain lain. Launcher muncul, chat tidak terhubung. Itu memang begitu.

apa yang ditambahkan ke bundle-mu

Tidak ada. Launcher datang dari CDN kami sebagai file tersendiri, jadi bukan bagian dari bundle JavaScript-mu, dan angka “First Load JS” yang dicetak next build tetap sama persis. React juga tidak disentuh: tombolnya hidup di shadow root sendiri, jadi CSS-mu tidak bisa merusaknya dan CSS-nya tidak bisa bocor ke aplikasimu. Satu-satunya yang perlu diketahui build-mu hanyalah tag itu.

checklist

  • Satu tag atau <Script> di root layout (app/layout.tsx atau pages/_app.tsx).
  • Config, kalau ada, berjalan sebelum launcher.
  • Tanda tangan pengguna dihitung hanya di server.
  • CSP mengizinkan CDN dan koneksi chat, kalau kamu punya CSP.
  • Sudah dicek di build produksi: satu request sebelum klik (dua, keduanya dari cdn.spookat.com, kalau kodemu mengatur style yang tidak kamu publikasikan), tanpa cookie.

Biayanya $4.99 per bulan per situs di starter, dan trial-nya 14 hari tanpa kartu.

suka? masuk daftar tunggu.

undangan dikirim bertahap. begitu punyamu datang: gratis 14 hari, tanpa kartu.

kami cuma mengirim email soal undanganmu dan peluncuran. kamu bisa berhenti kapan saja. privasi