Add live chat to a React app

In a React app built with Vite, the snippet goes in index.html, not in a component. The page loads once and React Router only changes what's inside it, so the chat stays put when visitors move between pages.

Tested with React 19.3, React Router 8.5 and Vite 8.3 on 2 November 2026, on the dev server and in a production build.

Before you start

HTML
<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.

Add the snippet

  1. Open index.html in the project root.
  2. Paste the two lines just before </body>, after the line that loads your app. Change the first line to window.$pidge || [], so commands your components push early are kept.
<body>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx"></script>
  <script>window.$pidge = window.$pidge || []; window.PIDGE_WEBSITE_ID = "YOUR-WEBSITE-ID";</script>
  <script src="https://app.pidge.chat/client/l.js" async></script>
</body>
  1. Run npm run build and deploy as usual. Vite copies both lines into the built index.html as they are.

The chat adds itself next to #root, not inside it, so React never renders over it. Nothing in your components needs to change.

Page changes with React Router

You don't need to do anything. Pidge notices when the address changes, so <Link> clicks and the back button keep the same chat, open or closed, and the inbox shows which page the visitor is on. We checked this with BrowserRouter: the conversation stayed open across pages and each route showed up in the inbox.

Why not a useEffect?

You can load the script from a component, but it buys you nothing and adds two catches:

If you've already written one, replacing it with the two lines in index.html is the simpler fix.

Keep the website ID in an env file

Vite fills in %VITE_…% names in index.html, in development and in the build. Put the ID in .env:

VITE_PIDGE_WEBSITE_ID=YOUR-WEBSITE-ID

and use it in the first line of the snippet:

<script>window.$pidge = window.$pidge || []; window.PIDGE_WEBSITE_ID = "%VITE_PIDGE_WEBSITE_ID%";</script>

If the variable isn't set where you build, Vite prints a warning and leaves %VITE_PIDGE_WEBSITE_ID% in the page as written, and the chat won't start. Check the build log, or view the page source, if the bubble is missing after a deploy.

Open the chat from your own components

Push commands to window.$pidge from an event handler. In TypeScript, declare it once:

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

To pass a logged-in customer's name, push it from an effect:

useEffect(() => {
  window.$pidge = window.$pidge || [];
  window.$pidge.push(["set", "user:nickname", [user.name]]);
}, [user.name]);

StrictMode sends it twice in development, which does no harm: the second one sets the same 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. The widget commands list everything else you can push.

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 your host gives the app a second address, like yourapp.netlify.app or yourapp.vercel.app, list that full address too if people visit it there.

To try the chat while you develop, add localhost to the list too. Pidge matches the host name, so any port works, including the localhost:5173 that Vite prints. 127.0.0.1 isn't accepted, so open your dev server at localhost.

Check that it works

  1. Open your site in a private window, so you're seeing what a visitor sees.
  2. The chat bubble should be in the bottom corner. Send yourself a message and answer it from the inbox.
  3. Click through two or three pages and check the chat is still there.
  4. 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 your host is still serving an older build.

Vite's own guide: vite.dev/guide/env-and-mode. React on StrictMode: react.dev/reference/react/StrictMode

Other platforms

Stuck, or did a step look different on your screen? Tell us in the chat on this page.

Get your snippet

Start free

The free plan covers one website and two people replying. No card needed.