USE CASE
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.
Published
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 for coding agents.
Ready before you edit the config
Make a Deacon account 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.
// 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 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
npm run devserves the app athttp://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.- Reload, and the button shows in the corner, under the label Help unless you set your own.
- Ask something your docs answer. Test chats on localhost are free.
- Before deploying,
npm run buildandnpx nuxt previewserve the production build. Stopnuxt devfirst, so the preview gets port 3000 too, or typelocalhostunder 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.
curl -s "https://heydeacon.com/api/widget/seen?k=pk_your_widget_key"
Try Deacon for free
Install DeaconServer 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 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. Swap in your own auth’s composable if you use something else. It’s a new file, so nothing else changes.
// 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.
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.
Try it on your own documentation
Add your help pages, ask it the question you know your docs cannot answer, and watch it say so.
The free plan covers 50 answers a month on one seat, and the paid plans are on the pricing page.
Free plan, no card.
READ NEXT
AI customer support for SaaS without a support team
Which questions AI should answer at a small SaaS, which must still reach you, and how to set one up on your site and inside your signed-in app.
How to add a chatbot to your website in 4 steps
How to add a chatbot to your website in four steps, where its script tag goes on each builder and framework, and how to tell it who’s logged in.
What to do in the week after launching a website
The checks to run before you post the link, the four numbers worth watching on launch day and what to do with the questions your first visitors send.