Deux routeurs, deux emplacements, un seul principe : le widget est un script tiers, il se charge après l'hydratation et n'entre jamais dans le rendu React.
Prérequis : le site est créé dans CONTROL, ses domaines sont déclarés, sa clé publique est copiée. Voir le README.
# .env.local # Clé PUBLIQUE : elle finit dans le bundle JavaScript, et c'est normal — elle vit de # toute façon dans le HTML de la page. Ce qui la protège est la liste de domaines # autorisés, côté serveur. NEXT_PUBLIC_INOCHAT_KEY=INOCHT_PUB_votre_cle_publique NEXT_PUBLIC_INOCHAT_ENDPOINT=https://messagerie.inovertix.com/api/inochat
INOCHAT n'a qu'une sorte de clé, publique. Il n'y a donc pas ici le piège de la clé secrète inlinée qui guette avec INODESK ou INOMAIL : rien de sensible ne peut partir dans le bundle.
// app/layout.jsx import Script from "next/script"; export default function RootLayout({ children }) { return ( <html lang="fr"> <body> {children} <Script src="https://messagerie.inovertix.com/inochat.js" data-site={process.env.NEXT_PUBLIC_INOCHAT_KEY} data-endpoint={process.env.NEXT_PUBLIC_INOCHAT_ENDPOINT} strategy="lazyOnload" /> </body> </html> ); }
strategy="lazyOnload" et non afterInteractive : la bulle n'a aucune raison de concurrencer le contenu de la page. Un visiteur qui veut discuter le fera une seconde après l'affichage, pas pendant.
strategy="lazyOnload"
afterInteractive
// pages/_app.jsx import Script from "next/script"; export default function App({ Component, pageProps }) { return ( <> <Component {...pageProps} /> <Script src="https://messagerie.inovertix.com/inochat.js" data-site={process.env.NEXT_PUBLIC_INOCHAT_KEY} data-endpoint={process.env.NEXT_PUBLIC_INOCHAT_ENDPOINT} strategy="lazyOnload" /> </> ); }
Retirez la bulle avec data-launcher="off" et appelez l'API. Le garde typeof window est indispensable : le composant est aussi rendu côté serveur, où window n'existe pas.
data-launcher="off"
typeof window
window
"use client"; export default function AskButton() { const open = () => { if (typeof window !== "undefined" && window.INOCHAT) { window.INOCHAT.open(); } }; return ( <button type="button" onClick={open}> Une question ? Discutez avec nous </button> ); }
Next.js ne recharge pas la page entre deux routes : le widget survit à la navigation, et la conversation en cours reste ouverte. C'est le comportement voulu — un visiteur qui pose une question puis clique sur « Livraison » ne doit pas perdre son fil.
Le script étant monté dans le layout racine, il n'est chargé qu'une fois. Ne le posez pas dans une page : à chaque retour sur cette page, next/script le redéclencherait.
next/script
Si vous publiez une CSP dans next.config.js, autorisez notre domaine pour le script et pour les appels :
next.config.js
// next.config.js const csp = [ "script-src 'self' 'unsafe-inline' https://messagerie.inovertix.com", "connect-src 'self' https://messagerie.inovertix.com", "style-src 'self' 'unsafe-inline'", ].join("; ");
Le panneau vit dans un Shadow DOM avec ses styles à l'intérieur : style-src doit autoriser les styles en ligne, ou notre domaine.
style-src
grep -r "INOCHT_PUB_" .next/static/
En navigation privée, la conversation repart de zéro (le jeton vit dans le localStorage).
localStorage
Sur mobile, le panneau s'ouvre en plein écran et la saisie reste au-dessus du clavier.
Une question hors base déclenche la réponse de repli, puis la proposition de transmettre à l'équipe.
Voyez Dépannage.