# Vite and React AI support chat widget

Add Deacon to a Vite and React app with one line in index.html. It answers visitors from your docs and says so when it doesn’t know.

Deacon is an AI support chat for a React app built with Vite, and it goes in as a single line of `index.html`, with no npm package and no React component. It answers people from your docs and pages within seconds. Asked something those don’t cover, it admits it doesn’t know and offers a box for an email address, or a one-tap reply once your app passes Deacon the logged-in user. Leaving an address sends you an email with the question, unless you already have the dashboard open, and what you write back appears in their chat. The free plan comes with 50 answers a month, no card and no end date.

This covers React apps you built with Vite yourself and ones exported from Lovable or Bolt. We read Vite’s documentation on 25 October 2026, when 8.3 was current. A coding agent can read this page [as Markdown](https://heydeacon.com/resources/vite-react-ai-support-chat-widget.md).

## Two things to sort out first

Your key comes from a Deacon account. [Sign up for Deacon](https://heydeacon.com/register) and take it from the snippet on Install, where it’s the `pk_` value of `data-key`. And the address the app is served from must be on Install’s list. The one you signed up with is there, with its subdomains, but a free Lovable or Bolt project publishes on an address of its own, such as a `lovable.app` or `bolt.host` one, and that needs adding. There’s nothing to install.

Install opens on Lovable’s or Bolt’s own steps when it recognises the builder, with a prompt for that builder’s chat. For an app you work on in your own editor, choose My own code. Its Copy the prompt is for Claude Code, Cursor or Codex, and the agent adds the code and checks it’s live.

## index.html, the page every route starts from

Vite’s [guide](https://vite.dev/guide/) keeps `index.html` “front-and-central” at the project root, and says that in development it “is the entry point to your application”. So a tag placed there is on every route. Add Deacon’s as the last line before `</body>`, under your app’s own module script. That one line is the whole change.

```html
<!-- index.html, at the project root -->
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Your app</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
    <script src="https://heydeacon.com/widget/v1/loader.js" data-key="pk_your_widget_key" async></script>
  </body>
</html>
```

Vite’s [features guide](https://vite.dev/guide/features) lists what it bundles from this file, such as `<script type="module" src>`, and a classic script with a full address isn’t on that list. We checked on 25 October 2026 by building a Vite 8 project with this file, and the Deacon line came out of `vite build` byte for byte.

## Dev server, then preview build

Vite serves development and the preview build on different ports, 5173 and 4173 by default. Install’s one-click add lists the address with the port it found, so either type `localhost` under Your sites on Install, which covers every port, or add `localhost:4173` too when Install names it.

1. `npm run dev`, then open `http://localhost:5173`. No button yet. Localhost isn’t listed, so the script hides itself. Install notices and shows “Found the code on localhost:5173”, with a one-click add.
2. Refresh. Deacon’s button, Help unless you renamed it, is now in the corner.
3. Ask a question your docs answer. Nothing asked on localhost is counted.
4. `npm run build` and `npm run preview`, then open the preview on port 4173 and look again. This is the `index.html` your host will serve.

Once it’s published, Install shows the app’s domain as Live and when Deacon was last seen. On the live app, client-side routing, such as React Router’s, moves between screens without reloading `index.html`, so the script loads once a visit and counts each new screen as a page view.

For an agent, there’s an answer without a browser. `latest` says which address last loaded Deacon, and `refused` names a development address it wouldn’t run on. [Deacon’s install guide](https://heydeacon.com/agent/install.md) has the rest.

```sh
curl -s "https://heydeacon.com/api/widget/seen?k=pk_your_widget_key"
```

## Built in Lovable or Bolt

Lovable’s [FAQ](https://docs.lovable.dev/introduction/faq) says its apps made before 13 May 2026 are React and Vite, and newer ones use TanStack Start. Whichever yours is, pasting the snippet into the Lovable chat and asking it to place the tag is quickest, as [Deacon and Lovable](https://heydeacon.com/resources/deacon-and-lovable) explains.

Bolt, its [supported technologies page](https://support.bolt.new/reference/supported-technologies) says, takes any JavaScript framework that runs in the browser. When your project has `index.html` at its root, edit it in Code view or ask Bolt to, as [Deacon and Bolt](https://heydeacon.com/resources/deacon-and-bolt) shows.

## Supabase logins

When people log in to your app, pass Deacon who they are, so their name shows on the conversation and a question Deacon can’t answer gets a reply to their email in one tap. Deacon uses these details as a label only. It doesn’t check them, and the AI never sees them.

A single-page app learns about logins after it has loaded, so pass them from the listener that hears them. With Supabase, which both Lovable and Bolt can connect to, that’s `onAuthStateChange`, safe to use “without an async function as callback”, by its [reference](https://supabase.com/docs/reference/javascript/auth-onauthstatechange). Add this beside your Supabase client, in the file that creates it.

```ts
// beside createClient(), such as in src/lib/supabase.ts
supabase.auth.onAuthStateChange((_event, session) => {
  const user = session?.user;

  window.deaconUser = user
    ? { id: user.id, email: user.email, name: user.user_metadata?.full_name }
    : null;
  window.Deacon?.identify(window.deaconUser);
});

declare global {
  interface Window {
    deaconUser?: { id?: string | number; email?: string; name?: string } | null;
    Deacon?: {
      identify(user: { id?: string | number; email?: string; name?: string } | null): void;
      open(): void;
    };
  }
}
```

The callback can fire before or after Deacon’s script runs, and setting the variable as well as calling `identify` covers both. A `null` after someone logs out makes the next person on that computer a new visitor. If the name lives somewhere other than `full_name` in your user metadata, read it from there. To check it, log in on localhost and open any screen, and Install’s “Do your users log in?” card says when it has seen you.

## What you learn from the chat

Visitors get an answer without leaving the screen they’re stuck on. You get the other half. Each question your docs missed sits in the dashboard with an Answer this button, and Topics groups what people ask and counts the people behind each topic. Bug reports land next to the JavaScript errors your app throws for them, and each copies for your coding agent in one click.

> The chat button takes your colours and can open from a button of your own.
> [See the website chat widget](https://heydeacon.com/features/website-chat-widget)

## Vite and React questions

### I pasted the tag and see no chat button. Why?

Your browser’s console will have a `[Deacon]` line saying why. “isn’t in your allowed domains” is the usual one on a builder, where the published `lovable.app` or `bolt.host` address, or a preview, isn’t on Install’s list. “Unknown widget key” means the `data-key` isn’t yours, and “could not start” is followed by the error that stopped it. No `[Deacon]` line at all usually means the build you’re looking at predates the change, so rebuild and hard-refresh, or that the button is hidden in Appearance. If the button is there but the chat opens blank, a line saying the chat frame “did not load” points to a Content Security Policy without `https://heydeacon.com` in `frame-src`.

### Is there a React component for Deacon?

No, and you don’t need one. The tag in `index.html` is the whole install. If something also loads the script a second time, Deacon notices it’s already running and ignores the copy.

### How do I open the chat from my own button?

Hide Deacon’s button in Appearance, then call `window.Deacon?.open()` in your button’s click handler. The declaration above types it.
