domanda con risposta, vendita salvatala chat non chiede il banner dei cookieteam illimitatorispondi da slack o dalla nostra app

/blog / guide

aggiungi una live chat a un sito next.js

· di kuba · 6 min di lettura

Una spina con l’etichetta next.js che sta per entrare in una presa rosa, con il gatto di Spookat a cavallo del cavo.

La live chat in Next.js è un tag script nel layout principale. Questa guida mostra dove va con l’app router e con il pages router, come passare l’aspetto del widget e l’utente connesso senza rompere l’ordine di caricamento, cosa aggiungere a una Content Security Policy e come controllare il risultato in una build di produzione.

la versione breve

Con l’app router, aggiungi il tag in app/layout.tsx, dentro <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>
  );
}

Sostituisci YOUR_SITE_KEY con la chiave della pagina di installazione nella dashboard. È tutta l’installazione quando l’aspetto sta nella dashboard, che è il caso predefinito. Il resto della guida serve quando vuoi più controllo. Se parti da zero, la guida principale copre la parte dell’account.

perché il layout principale

Il layout principale avvolge ogni route e resta montato mentre l’utente naviga. Il launcher si aggancia alla pagina una sola volta. La navigazione lato client non ricarica la pagina, quindi non ricarica nemmeno il launcher, e un visitatore può tenere aperta una conversazione mentre clicca in giro.

Metti il tag in un componente di pagina e la chat esiste solo su quella pagina. Mettilo in un componente che viene renderizzato su più pagine e avrai duplicati nei ricaricamenti completi (il launcher ignora la propria seconda copia, ma resta una richiesta sprecata).

con next/script

next/script è il modo di Next.js per caricare script di terze parti, e qui funziona bene:

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 carica il launcher subito dopo l’idratazione. lazyOnload aspetta che il browser sia inattivo, e sui telefoni lenti fa comparire il pulsante un po’ più tardi. Vanno bene entrambi per un launcher da 2 kb; afterInteractive mostra il pulsante prima. Non usare beforeInteractive per il launcher: entrerebbe in competizione con il tuo codice per niente.

impostare l’aspetto nel codice

L’aspetto che pubblichi nella dashboard viaggia dentro il launcher. Se preferisci tenerlo nel repository, imposta window.Spookat prima che il launcher venga eseguito. La configurazione della pagina vince su quella della dashboard.

L’ordine di caricamento conta: la configurazione deve esistere quando il launcher parte. Con next/script, dai beforeInteractive alla configurazione (consentito nel layout principale) e afterInteractive al launcher:

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

Gli script beforeInteractive girano prima dell’idratazione, quelli afterInteractive dopo, quindi l’ordine è garantito. Le chiavi sono le stesse di ovunque: style (uno dei 8), font, accent, side, greeting, label, lang. La documentazione le elenca tutte.

il pages router

Con il pages router, il wrapper condiviso è 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" />
    </>
  );
}

Per una configurazione, nel pages router gli script beforeInteractive vanno in pages/_document.tsx. Metti lì lo script di window.Spookat, dentro <Head>, con lo stesso id e la stessa strategy di sopra, e lascia il launcher in _app.tsx.

l’utente connesso

Da pro in su, puoi dire alla chat chi ha effettuato l’accesso. Il tuo team vedrà logged in as 42 sulla scheda di Slack, e i webhook, l’export e il read_convo del tuo agente porteranno l’id. L’id viene firmato con un segreto che conosce solo il tuo server, così nessuno può fingersi qualcun altro.

Crea il segreto di identità nella pagina di installazione della dashboard, mettilo in una variabile d’ambiente e firma sul server. Con l’app router il layout principale è un componente server, il che rende tutto semplice:

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

Non calcolare mai la firma in un componente client: il segreto finirebbe nel browser. Una firma mancante o sbagliata non è un errore; semplicemente la chat non dice chi è. Leggere la sessione nel layout principale fa renderizzare ogni pagina a ogni richiesta, quindi se oggi il tuo layout è statico, valutalo prima di aggiungerlo.

content security policy

Se la tua app invia un header Content-Security-Policy, il widget ha bisogno di:

  • script-src e style-src: https://cdn.spookat.com
  • font-src: https://cdn.spookat.com, se hai scelto un font ospitato
  • connect-src: wss://api.spookat.com, per la connessione della chat dopo un clic
  • img-src: data:, per i loghi e le foto del team
  • style-src: il launcher inietta un piccolo blocco <style> nella propria shadow root, quindi gli stili inline devono essere consentiti per lui

Senza queste voci, la console del browser dice esattamente quale direttiva ha bloccato cosa. Aggiungi la sorgente che indica e ricarica.

controlla in una build di produzione

La modalità di sviluppo aggiunge i suoi script e l’hot reload, quindi controlla una build vera:

next build && next start

Apri http://localhost:3000 in una finestra privata con i DevTools aperti. Il dominio conta: la chat si connette solo dal dominio che hai registrato e dai suoi sottodomini, quindi su localhost vedrai il launcher, ma la chat non si connetterà. La chat vera e propria provala sul sito pubblicato.

Su entrambi:

  1. Network, “Disable cache”, ricarica, filtra per spookat: una richiesta, il launcher da cdn.spookat.com. Due, entrambe da cdn.spookat.com, se il tuo codice imposta in window.Spookat uno stile diverso da quello che hai pubblicato. Niente verso api.spookat.com.
  2. Application: nessun cookie di Spookat e nessuno storage fino al primo messaggio.
  3. Performance o Lighthouse: il launcher non dovrebbe comparire come un long task. Confronta qualche esecuzione con e senza; la differenza dovrebbe restare nel normale rumore tra un’esecuzione e l’altra.
  4. Clicca sul launcher sul sito pubblicato: il pannello si carica, la connessione si apre, il tuo messaggio arriva in Slack.

destinazioni di deploy

Niente di tutto questo dipende da dove gira l’app. Vercel, un server Node, un container o output: "export" per un sito statico servono tutti lo stesso HTML, e il tag funziona allo stesso modo ovunque. Con un export statico, l’utente connesso deve arrivare da un server che controlli, perché non c’è un rendering lato server che possa firmarlo.

collega le risposte

Quando la chat è sul sito, collega Slack nella dashboard e scegli un canale. La guida Slack ti accompagna. Se usi Claude Code, la guida Claude Code mostra come far fare tutto questo all’agente.

app multilingue

Se la tua app ha un segmento [locale], di’ al widget in che lingua è la pagina, così il pannello segue la pagina e non il browser del visitatore. Il widget parla 12 lingue; lang accetta codici come en, pl, de. In app/[locale]/layout.tsx:

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

Una lingua che il widget non ha ripiega su quella del browser del visitatore, poi sull’inglese. Il tuo team vede comunque la lingua sulla chat.

errori comuni

  • Il tag in un componente client con "use client". Viene renderizzato, ma si idrata insieme al componente, e ogni rimontaggio è un’altra occasione per un duplicato. Tienilo nel layout.
  • Creare lo script in useEffect. Più codice per lo stesso risultato, e si carica più tardi di afterInteractive.
  • type="module" sul tag. Il launcher è uno script classico; non mettere il tipo.
  • Precaricare il pannello. Un <link rel="preload"> per lo script del pannello sposta il suo download prima del clic per ogni visitatore. Il punto è proprio che aspetti.
  • Provare la chat su un deploy di anteprima. Gli URL di anteprima sono altri domini. Il launcher compare, la chat non si connette. È normale.

cosa aggiunge al tuo bundle

Niente. Il launcher arriva dalla nostra CDN come file a sé, quindi non fa parte del tuo bundle JavaScript, e i numeri di “First Load JS” che stampa next build restano identici. Non tocca nemmeno React: il pulsante vive nella sua shadow root, quindi il tuo CSS non può romperlo e il suo CSS non può finire nella tua app. L’unica cosa che la tua build deve sapere è il tag.

checklist

  • Un tag o uno <Script> nel layout principale (app/layout.tsx o pages/_app.tsx).
  • La configurazione, se c’è, viene eseguita prima del launcher.
  • Le firme utente vengono calcolate solo sul server.
  • La CSP consente la CDN e la connessione della chat, se hai una CSP.
  • Controllato in una build di produzione: una richiesta prima di un clic (due, entrambe da cdn.spookat.com, se il tuo codice imposta uno stile che non hai pubblicato), nessun cookie.

Costa 4,99 € al mese per sito con starter, e la prova dura 14 giorni senza carta.

ti è piaciuto? mettiti in lista.

gli inviti partono a piccoli gruppi. quando arriva il tuo: 14 giorni gratis, senza carta.

ti scriviamo solo per l'invito e il lancio. puoi smettere quando vuoi. privacy