Vue and Nuxt
Mount the public loader once in a browser-only Vue component or Nuxt plugin.
Mount the public loader once in a browser-only Vue component or Nuxt plugin.
Prepare the embed#
Publish the site, enable embedding, and add the exact origin for local, preview, and production deployments. Decide whether the agent belongs to the whole application or only selected route components.
Nuxt site-wide plugin#
Create a .client.ts plugin so no DOM code runs during server rendering. Nuxt loads it once in the browser, which keeps the launcher and conversation stable across client navigation.
// plugins/make-agent-fast.client.ts
const slug = "YOUR_SITE_SLUG";
export default defineNuxtPlugin(() => {
if (document.getElementById(`maf-embed-root-${slug}`)) return;
const script = document.createElement("script");
script.id = "maf-agent";
script.src = "https://makeagent.fast/embed.js";
script.dataset.agent = slug;
script.dataset.position = "right";
script.dataset.label = "Ask us";
script.async = true;
document.body.appendChild(script);
});Do not put a secret in runtimeConfig.public; only the public site slug and visual settings belong in the browser. Real MAF API/provider keys stay in private server runtime configuration.
The site-wide plugin intentionally has no unmount cleanup: it belongs to the Nuxt application lifetime. During hot-module replacement in development, a prior root can remain until a full reload; the root guard prevents a second launcher. Verify final behavior with a clean production build rather than repeatedly editing the plugin in one long-lived tab.
Route-scoped Vue component#
For selected routes, mount in onMounted and remove both the script and generated root in onBeforeUnmount.
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from "vue";
const slug = "YOUR_SITE_SLUG";
let script: HTMLScriptElement | null = null;
onMounted(() => {
if (document.getElementById(`maf-embed-root-${slug}`)) return;
script = document.createElement("script");
script.id = "maf-agent";
script.src = "https://makeagent.fast/embed.js";
script.dataset.agent = slug;
script.async = true;
document.body.appendChild(script);
});
onBeforeUnmount(() => {
script?.remove();
document.getElementById(`maf-embed-root-${slug}`)?.remove();
});
</script>Use one component owner. Multiple components with different slugs intentionally create multiple agents, which is rarely desirable.
If a Vue application switches authenticated tenants without a full page load, treat that as an explicit teardown/remount. Remove the old slug's root, validate the new tenant's public slug on trusted application state, then append a new script. Changing script.dataset.agent after the loader ran does not retarget the existing iframe.
Host security headers still apply to Nuxt/Vue output. Add the loader/frame origin to CSP and permit microphone only when voice is enabled; a client-only plugin cannot override a response-level browser policy.
Verify SSR and routing#
- Run the production Nuxt/Vue build and confirm no
document is not definederror. - Direct-load a route with the embed and confirm one root.
- Navigate client-side and confirm site-wide versus route-scoped behavior.
- Inspect
embed.js, iframe, CSP, and exact origin. - Send a message and test mobile/microphone behavior.
Troubleshooting#
| Symptom | Fix |
|---|---|
| SSR crashes | Rename the Nuxt plugin to .client.ts or keep DOM work in onMounted |
| Launcher remains after leaving route | Remove the slug-specific root in onBeforeUnmount |
| Launcher resets on every route | Use the site-wide client plugin/layout instead of a page component |
| Preview domain fails | Add that exact HTTPS origin separately |
| Script is inserted twice | Use one stable owner and root/ID guard |
No npm Vue/Nuxt adapter is currently published; this guide uses the supported hosted loader directly.