yanıtlanan soru, kurtarılan satışsohbet çerez banner'ı istemezsınırsız ekip arkadaşıslack ya da uygulamamızdan yanıt

/blog / rehberler

bir next.js sitesine canlı sohbet ekleyin

· yazan: kuba · 4 dk okuma

Pembe bir prize girmek üzere olan next.js etiketli bir fiş ve kablonun üzerinde duran Spookat kedisi.

Next.js’te canlı sohbet, kök layout’ta tek bir script etiketidir. Bu rehber, etiketin app router ve pages router’da nereye gittiğini, yükleme sırasını bozmadan widget’ın görünümünü ve giriş yapmış kullanıcıyı nasıl ileteceğinizi, bir Content Security Policy’ye neler ekleneceğini ve sonucu bir production build’de nasıl kontrol edeceğinizi gösteriyor.

kısa versiyon

App router’da etiketi app/layout.tsx içinde, <body> içine ekleyin:

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>
  );
}

YOUR_SITE_KEY yerine paneldeki kurulum sayfasından aldığınız anahtarı yazın. Görünüm panelde durduğunda, ki varsayılan budur, kurulumun tamamı bu. Rehberin geri kalanı daha fazla kontrol istediğiniz durumlar için. Sıfırdan başlıyorsanız, hesap tarafını ana rehber anlatıyor.

neden kök layout

Kök layout her rotayı sarar ve kullanıcı gezinirken bağlı kalır. Başlatıcı kendini sayfaya bir kez bağlar. İstemci tarafı gezinme sayfayı yeniden yüklemez, dolayısıyla başlatıcıyı da yeniden yüklemez ve ziyaretçi sitede dolaşırken bir konuşmayı açık tutabilir.

Etiketi bir sayfa bileşenine koyarsanız sohbet yalnızca o sayfada olur. Birkaç sayfada render edilen bir bileşene koyarsanız, tam yeniden yüklemelerde kopyalar oluşur (başlatıcı kendi ikinci kopyasını yok sayar ama yine de boşa giden bir istektir).

next/script ile

next/script, Next.js’te üçüncü taraf betikleri yüklemenin yoludur ve burada gayet iyi çalışır:

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, başlatıcıyı hidrasyondan hemen sonra yükler. lazyOnload tarayıcının boşa çıkmasını bekler, bu da yavaş telefonlarda düğmenin görünmesini biraz geciktirir. 2 kb’lık bir başlatıcı için ikisi de uygundur; afterInteractive düğmeyi daha erken gösterir. Başlatıcı için beforeInteractive kullanmayın: boşuna kendi kodunuzla yarışır.

görünümü kodda ayarlamak

Panelde yayımladığınız görünüm başlatıcının içinde gelir. Onu depoda tutmayı tercih ederseniz, başlatıcı çalışmadan önce window.Spookat ayarlayın. Sayfanın yapılandırması panelinkinin önüne geçer.

Yükleme sırası önemlidir: başlatıcı çalıştığında yapılandırmanın var olması gerekir. next/script ile yapılandırmaya beforeInteractive (kök layout’ta izinli), başlatıcıya afterInteractive verin:

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>
  );
}

beforeInteractive betikleri hidrasyondan önce, afterInteractive olanlar sonra çalışır, bu yüzden sıra garantidir. Anahtarlar her yerdekiyle aynıdır: style (8 taneden biri), font, accent, side, greeting, label, lang. Hepsi dokümanlarda listeleniyor.

pages router

Pages router’da ortak sarmalayıcı pages/_app.tsx dosyasıdır:

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" />
    </>
  );
}

Bir yapılandırma için, pages router’da beforeInteractive betikleri pages/_document.tsx içine gider. window.Spookat betiğini oraya, <Head> içine, yukarıdakiyle aynı id ve strategy ile koyun ve başlatıcıyı _app.tsx içinde bırakın.

giriş yapmış kullanıcı

pro ve üstünde, sohbete kimin giriş yaptığını söyleyebilirsiniz. Ekibiniz o zaman Slack kartında logged in as 42 görür; webhook’lar, dışa aktarma ve ajanınızın read_convo aracı da kimliği taşır. Kimlik yalnızca sunucunuzun bildiği bir secret ile imzalanır, böylece kimse başkası gibi davranamaz.

Kimlik secret’ını paneldeki kurulum sayfasında oluşturun, bir ortam değişkenine koyun ve sunucuda imzalayın. App router’da kök layout bir sunucu bileşenidir, bu da işi kolaylaştırır:

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>
  );
}

İmzayı asla bir istemci bileşeninde hesaplamayın: secret tarayıcıya gider. Eksik ya da yanlış bir imza hata değildir; sohbet sadece kim olduğunu söylemez. Oturumu kök layout’ta okumak her sayfanın istek başına render edilmesine yol açar, bu yüzden layout’unuz bugün statikse eklemeden önce bunu tartın.

content security policy

Uygulamanız bir Content-Security-Policy header’ı gönderiyorsa, widget’ın ihtiyaç duyduğu şunlar:

  • script-src ve style-src: https://cdn.spookat.com
  • font-src: barındırılan bir font seçtiyseniz https://cdn.spookat.com
  • connect-src: tıklamadan sonraki sohbet bağlantısı için wss://api.spookat.com
  • img-src: logolar ve ekip arkadaşlarının fotoğrafları için data:
  • style-src: başlatıcı kendi shadow root’una küçük bir <style> bloğu ekler, bu yüzden onun için satır içi stillere izin verilmelidir

Bunlar olmadan, tarayıcı konsolu hangi direktifin neyi engellediğini tam olarak söyler. Söylediği kaynağı ekleyin ve yeniden yükleyin.

bir production build’de kontrol edin

Geliştirme modu kendi betiklerini ve hot reload’u ekler, bu yüzden gerçek bir build’i kontrol edin:

next build && next start

DevTools açıkken http://localhost:3000 adresini gizli bir pencerede açın. Alan adı önemlidir: sohbet yalnızca kaydettiğiniz alan adından ve alt alan adlarından bağlanır, bu yüzden localhost’ta başlatıcıyı görürsünüz ama sohbet bağlanmaz. Sohbetin kendisini deploy edilmiş sitenizde test edin.

İkisinde de:

  1. Network, “Disable cache”, yeniden yükleyin, spookat ile filtreleyin: tek istek, cdn.spookat.com adresinden başlatıcı. Kodunuz window.Spookat içinde yayınladığınızdan farklı bir stil ayarlıyorsa iki istek, ikisi de cdn.spookat.com adresinden. api.spookat.com adresine hiçbir şey yok.
  2. Application: Spookat’tan çerez yok ve ilk mesaja kadar depolama yok.
  3. Performance ya da Lighthouse: başlatıcı uzun bir görev olarak görünmemeli. Onunla ve onsuz birkaç çalıştırmayı karşılaştırın; fark, çalıştırmalar arasındaki normal gürültünün içinde kalmalıdır.
  4. Deploy edilmiş sitede başlatıcıya tıklayın: panel yüklenir, bağlantı açılır, mesajınız Slack’e ulaşır.

deploy hedefleri

Buradaki hiçbir şey uygulamanın nerede çalıştığına bağlı değil. Vercel, bir Node sunucusu, bir container ya da statik bir site için output: "export" aynı HTML’i sunar ve etiket hepsinde aynı şekilde çalışır. Statik bir export’ta, giriş yapmış kullanıcının sizin kontrol ettiğiniz bir sunucudan gelmesi gerekir, çünkü onu imzalayacak bir sunucu render’ı yoktur.

yanıtları bağlayın

Sohbet sitede olduktan sonra panelde Slack’i bağlayın ve bir kanal seçin. Slack rehberi adım adım anlatıyor. Claude Code kullanıyorsanız, Claude Code rehberi yukarıdakilerin hepsini agent’a nasıl yaptıracağınızı gösteriyor.

çok dilli uygulamalar

Uygulamanızda bir [locale] segmenti varsa, widget’a sayfanın hangi dilde olduğunu söyleyin; böylece panel ziyaretçinin tarayıcısına değil sayfaya uyar. Widget 12 dil konuşur; lang, en, pl, de gibi kodlar alır. app/[locale]/layout.tsx içinde:

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

Widget’ta olmayan bir dil, ziyaretçinin tarayıcı diline, sonra İngilizceye düşer. Ekibiniz her durumda sohbette dili görür.

sık yapılan hatalar

  • Etiketin "use client" içeren bir istemci bileşeninde olması. Render edilir ama bileşenle birlikte hidrate olur ve her yeniden bağlanma bir kopya için yeni bir fırsattır. Layout’ta tutun.
  • Betiği useEffect içinde oluşturmak. Aynı sonuç için daha fazla kod ve afterInteractive’ten daha geç yüklenir.
  • Etikette type="module". Başlatıcı klasik bir betiktir; type eklemeyin.
  • Paneli önceden yüklemek. Panel betiği için bir <link rel="preload">, indirmeyi her ziyaretçi için tıklamanın önüne taşır. Bütün mesele beklemesidir.
  • Sohbeti bir önizleme deploy’unda test etmek. Önizleme URL’leri başka alan adlarıdır. Başlatıcı görünür, sohbet bağlanmaz. Beklenen budur.

bundle’ınıza ne ekler

Hiçbir şey. Başlatıcı CDN’imizden ayrı bir dosya olarak gelir, yani JavaScript bundle’ınızın parçası değildir ve next build komutunun yazdırdığı “First Load JS” rakamları tamamen aynı kalır. React’e de dokunmaz: düğme kendi shadow root’unda yaşar, bu yüzden sizin CSS’iniz onu bozamaz, onun CSS’i de uygulamanıza sızamaz. Build’inizin bilmesi gereken tek şey o etikettir.

kontrol listesi

  • Kök layout’ta tek bir etiket ya da <Script> (app/layout.tsx ya da pages/_app.tsx).
  • Varsa yapılandırma başlatıcıdan önce çalışıyor.
  • Kullanıcı imzaları yalnızca sunucuda hesaplanıyor.
  • CSP’niz varsa, CDN’e ve sohbet bağlantısına izin veriyor.
  • Bir production build’de kontrol edildi: tıklamadan önce tek istek (kodunuz yayınlamadığınız bir stil ayarlıyorsa iki, ikisi de cdn.spookat.com adresinden), hiç çerez yok.

starter ile site başına aylık $4.99 tutar ve deneme süresi kartsız 14 gündür.

beğendiniz mi? listeye katılın.

davetler küçük gruplar halinde gidiyor. seninki gelince: 14 gün ücretsiz, kart yok.

size yalnızca davetiniz ve lansman hakkında yazarız. istediğiniz zaman durdurabilirsiniz. gizlilik