Before you start
- Next.js 13 or later for the App Router (the
appfolder). The Pages Router works with any version that hasnext/script. - Your website ID from Websites in the Pidge inbox. This is the plain snippet it comes in:
<script>window.$pidge = []; window.PIDGE_WEBSITE_ID = "YOUR-WEBSITE-ID";</script> <script src="https://app.pidge.chat/client/l.js" async></script>
Your own copy is in the Pidge inbox under Websites, in Code for your website, with your website ID already filled in. If you've set up a chat domain, it loads from there instead.
In Next.js you don't paste those two lines as they are. You load them with the Script component, as below.
App Router: app/layout.tsx
Open your root layout, app/layout.tsx (or .js), and add the two
Script tags at the end of <body>:
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script id="pidge-setup" strategy="afterInteractive">
{`window.$pidge = window.$pidge || []; window.PIDGE_WEBSITE_ID = "YOUR-WEBSITE-ID";`}
</Script>
<Script src="https://app.pidge.chat/client/l.js" strategy="afterInteractive" />
</body>
</html>
);
}
- An inline
Scriptneeds anid, or Next.js can't track it. afterInteractiveis the default. It's written out so nobody changes it tobeforeInteractive, which isn't needed here.window.$pidge || []keeps any commands your components pushed before this line ran.- Keep it in the root layout. In a single page, it only loads when someone lands on that page.
The script loads once. Moving between pages with <Link> keeps the same
chat, open or closed, without loading it again.
Pages Router: pages/_app.tsx
With the pages folder, the same two tags go in your custom _app:
import type { AppProps } from "next/app";
import Script from "next/script";
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<Script id="pidge-setup" strategy="afterInteractive">
{`window.$pidge = window.$pidge || []; window.PIDGE_WEBSITE_ID = "YOUR-WEBSITE-ID";`}
</Script>
<Script src="https://app.pidge.chat/client/l.js" strategy="afterInteractive" />
</>
);
}
You don't need _document for this. Next.js only wants scripts there for
beforeInteractive.
Open the chat from your own components
$pidge.push runs in the browser, so call it from a client component. Server
components can't reach window.
"use client";
declare global {
interface Window {
$pidge?: { push: (...commands: unknown[]) => void } | unknown[];
}
}
export function ChatButton() {
return (
<button
type="button"
onClick={() => {
window.$pidge = window.$pidge || [];
window.$pidge.push(["do", "chat:open"]);
}}
>
Chat with us
</button>
);
}
The same pattern works in a useEffect, for example to pass a logged-in
customer's name with ["set", "user:nickname", [name]]. If the script hasn't
loaded yet, the command waits in the array and runs when it has. For emails your team can
trust, sign them on the server: see signed visitor emails.
Push ["do", "session:reset"] when someone logs out.
List your domains in Pidge
The chat only connects on domains you've listed for the website. In the inbox, open
Websites and put each domain on its own line in
Domains. Subdomains are included, so example.com also
covers www.example.com.
If people visit your vercel.app address (or your host's equivalent), list
that full address too, never just vercel.app. Preview deployments get their
own addresses, and the chat only connects on the ones you list.
To try the chat while you develop, add localhost to the list too. Pidge
matches the host name, so any port works. 127.0.0.1 isn't accepted, so open
your dev server at localhost.
Check that it works
- Open your site in a private window, so you're seeing what a visitor sees.
- The chat bubble should be in the bottom corner. Send yourself a message and answer it from the inbox.
- In the inbox, the website's Not installed yet label goes away after the first visit from a listed domain.
If the bubble shows but the chat won't connect, the domain you're on isn't in the website's list in Pidge. If there's no bubble at all, the snippet isn't on the page, or the platform is still serving an older copy of it.
Common problems
- No bubble, and an error in the browser console about Content Security Policy. If your app sets a CSP, allow the chat's address in
script-srcand inconnect-src(bothhttps:andwss:). The console message names the directive that blocked it. - Commands do nothing. Check that the code runs in a client component, and that it pushes to
window.$pidgerather than a copy taken before the script loaded.
Next.js's own guide: nextjs.org/docs/app/guides/scripts
Other platforms
Stuck, or did a step look different on your screen? Tell us in the chat on this page.