# Nuxt AI support chat widget

Add Deacon to a Nuxt app from nuxt.config.ts, at the end of every page’s body. It answers visitors from your docs and says so when it doesn’t know.

Deacon is an AI support chat you configure once in `nuxt.config.ts`, and it then appears on every page of your Nuxt app. It reads your docs and pages and answers from them in seconds. For a question they leave open, Deacon says plainly that it doesn’t know and shows a box under its reply where the person can leave an email, or, for a logged-in user your app has passed it, offers a one-tap reply. When they leave an address, you’re told by email, if the dashboard isn’t already open in front of you, and you answer from there into their chat. Deacon’s free plan allows 50 answers a month and asks for no card, and it doesn’t expire.

Nuxt 4.6 was the current release when we read its docs on 25 October 2026. This page has a [Markdown version](https://heydeacon.com/resources/nuxt-ai-support-chat-widget.md) for coding agents.

## Ready before you edit the config

[Make a Deacon account](https://heydeacon.com/register) and Install shows a snippet whose `data-key`, starting `pk_`, is your key. The domain your Nuxt app runs on goes on Install’s list, and the one you gave at sign-up is listed already, along with its subdomains. No Nuxt module or npm package is involved.

If your home page is a Nuxt build, Install notices and says your coding agent can add it for you. Copy the prompt gives Claude Code, Cursor or Codex the tag and your key, and the agent puts it in and checks it’s live.

## One script in app.head

In Nuxt you don’t paste the snippet as HTML. You describe it in `app.head`, and Nuxt writes the tag into every page. If your config already has an `app.head`, the new part is the one entry under `script`.

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      script: [
        {
          src: 'https://heydeacon.com/widget/v1/loader.js',
          'data-key': 'pk_your_widget_key',
          async: true,
          tagPosition: 'bodyClose',
        },
      ],
    },
  },
});
```

Nuxt’s [SEO and meta guide](https://nuxt.com/docs/4.x/getting-started/seo-meta) says `app.head` sets the head “for your entire app”, and adds that it doesn’t take reactive data, which Deacon’s tag never needs.

## Why `tagPosition: 'bodyClose'`

Left alone, a script in `app.head` lands in `<head>`. The same guide says `tagPosition: 'bodyClose'` appends a tag “to the end of the `<body>` tag”, which is where Deacon’s Install page puts it. Nuxt renders head tags with Unhead, and when we rendered this exact object through Unhead 3 on 25 October 2026, the tag came out at the end of the body with `data-key` and `async` intact.

## Try it under nuxt dev

1. `npm run dev` serves the app at `http://localhost:3000`. Because localhost isn’t on the list yet, the script stays hidden on that first load, and Install, at the top of the dashboard, reports “Found the code on localhost:3000” with a button to add it.
2. Reload, and the button shows in the corner, under the label Help unless you set your own.
3. Ask something your docs answer. Test chats on localhost are free.
4. Before deploying, `npm run build` and `npx nuxt preview` serve the production build. Stop `nuxt dev` first, so the preview gets port 3000 too, or type `localhost` under Your sites on Install, which covers every port.

When the deploy is out, Install marks the domain Live and shows when Deacon was last seen there. On the live site, moving between pages doesn’t reload the document, so the chat stays put and each route change is counted as a page view.

Agents get the same check over HTTP. The response’s `latest` is the address that most recently loaded Deacon, and `refused` a development address left off the list. See [Deacon’s install guide](https://heydeacon.com/agent/install.md).

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

## Server rendering and nuxt generate

The tag is part of the HTML Nuxt sends, whether a server renders each request or `nuxt generate` writes the pages out ahead of time. A static marketing site needs nothing past `app.head`.

## A client plugin for logged-in users

For an app with accounts, hand Deacon the person behind each chat, so their name is on the conversation and they don’t have to type an email to hear back. Deacon keeps these details as a label. It doesn’t verify them, and the AI never sees them.

Nuxt’s [plugins guide](https://nuxt.com/docs/4.x/directory-structure/app/plugins) registers any file in `app/plugins/` by itself, and a `.client` suffix keeps one in the browser. This one watches the session from [nuxt-auth-utils](https://github.com/atinux/nuxt-auth-utils). Swap in your own auth’s composable if you use something else. It’s a new file, so nothing else changes.

```ts
// app/plugins/deacon.client.ts
export default defineNuxtPlugin(() => {
  const { ready, user } = useUserSession();

  watch(
    [ready, user],
    ([isReady, current]) => {
      // Wait until the session check is done, so a logged-in user isn't passed as nobody.
      if (!isReady) {
        return;
      }

      window.deaconUser = current
        ? { id: current.id, email: current.email, name: current.name }
        : null;
      window.Deacon?.identify(window.deaconUser);
    },
    { immediate: true },
  );
});

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

Waiting for `ready` matters. Passing `null` while the session is still loading would start a logged-in person over as a new visitor on every page load. Once the session is known, `null` after a log-out is right, and the next person on that computer starts fresh. Log in under `nuxt dev` and Install’s “Do your users log in?” card confirms it has seen you.

## What Nuxt visitors get, and what you learn

Your visitors get answers at any hour without leaving the page. You get a record of what they needed. Deacon lists the questions it couldn’t answer, each with an Answer this button that teaches it for next time, and Topics groups what people ask and counts the people behind each topic.

> Deacon groups what your visitors ask and counts the people behind each question.
> [See chatbot analytics](https://heydeacon.com/features/chatbot-analytics)

## Nuxt questions

### Nothing appears after I add the script. What should I check?

Deacon writes a line beginning `[Deacon]` to the browser console when it stops itself. “isn’t in your allowed domains” names the address to add on Install, “Unknown widget key” means `data-key` doesn’t match your snippet, and “could not start” carries the error that stopped it. If there’s no `[Deacon]` line, view the page source for `loader.js`. If it’s missing, the entry isn’t in the `app.head` of the config this app builds from, and if it’s there, check the button isn’t hidden in Appearance. When the button opens a chat that never fills in, look for a line about the chat frame that “did not load”, which means a Content Security Policy is missing `https://heydeacon.com` in `frame-src`.

### Can I use useHead in app.vue instead?

Yes. The guide recommends `useHead()` in `app.vue` for head data that changes, and the same script object works there. Deacon’s tag is the same on every page, so `app.head` is enough.

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

Hide Deacon’s button in Appearance and call `window.Deacon?.open()` from a click handler. The declaration above includes it.
