Skip to page content
Docs
Make Agent Fast documentation

React and Next.js

Mount the public loader once in a client-owned React or Next.js layout and clean it up deliberately.

At a glance

Mount the public loader once in a client-owned React or Next.js layout and clean it up deliberately.

Embed install pathLoader on your site talks to the hosted agent runtime
Your websiteHTML / appLoader scriptembed.jsWidget UIChat / voiceAgent

Prepare origins and scope#

Publish the site, enable embedding, and allowlist the exact local/preview/production origins. Decide whether the launcher should survive every client navigation (root layout) or exist only under selected routes (nested layout/component).

Next.js App Router#

Use next/script in a component rendered by the layout that owns the launcher. The slug is safe to expose; do not use an API key or secret environment variable.

import Script from "next/script";

export function AgentEmbed() {
  return (
    <Script
      id="maf-agent"
      src="https://makeagent.fast/embed.js"
      strategy="afterInteractive"
      data-agent="YOUR_SITE_SLUG"
      data-position="right"
      data-label="Ask us"
    />
  );
}

Render <AgentEmbed /> once in app/layout.tsx for a site-wide agent or a nested layout for a route group. Do not render a separate copy from every page component. afterInteractive avoids server-side DOM access and does not block initial HTML parsing.

In the Next.js Pages Router, render the same component once from pages/_app.tsx for site-wide behavior. Rendering it from an individual page is appropriate only when leaving that page should remove the agent; in that case use the explicit effect/cleanup pattern below because a loaded external script has already created DOM outside React's tree.

React without Next.js#

Create the script in an effect. A stable script ID prevents duplicate insertion under development Strict Mode. If the component is route-scoped, cleanup must remove both the script and the root created by the loader.

import { useEffect } from "react";

const slug = "YOUR_SITE_SLUG";

export function AgentEmbed() {
  useEffect(() => {
    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.async = true;
    document.body.appendChild(script);

    return () => {
      script.remove();
      document.getElementById(`maf-embed-root-${slug}`)?.remove();
    };
  }, []);

  return null;
}

For a site-wide launcher, mount this component above the router and omit route-driven unmounting. Keeping the root preserves the iframe/conversation across navigation.

Environment configuration#

A build-time public variable may hold the slug because it is not secret. Validate it before rendering and keep real MAF API keys/provider credentials server-only. A missing slug causes the loader to warn and exit rather than creating a launcher.

If your application serves different customer tenants from one React build, do not switch data-agent on an already-executed script. Give the current tenant one stable slug, remove the old slug's root during an authenticated tenant switch, and then mount a fresh script. Never let an untrusted query parameter choose a cross-tenant slug.

Verify hydration and navigation#

  1. Load a public route directly and confirm one launcher.
  2. Navigate client-side through routes that should retain it.
  3. For a nested/route-scoped install, navigate out and confirm both script/root disappear, then back and confirm one fresh launcher.
  4. Inspect Network/CSP and test a real message.
  5. Run the production build; development Strict Mode alone is not final behavior.

Troubleshooting#

SymptomFix
document is not definedDOM code ran during SSR; move it into useEffect or use next/script
Two launchers/agents appearMultiple slugs or components are mounted; keep one owner layout
Launcher remains after route exitRemove maf-embed-root-SLUG in cleanup, not only the script
Launcher disappears on every navigationMount higher in the layout/router tree
Works locally but not preview/productionAllowlist each deployment's exact origin

No npm React/Next adapter is currently published; these examples use the supported public REST-independent loader.