Skip to content
All articles

Embed a chatbot on Next.js in five minutes

The right way to add a chatbot widget to Next.js without hurting Largest Contentful Paint or hydration.

DGDeepak Gupta3 min read

Add a chatbot to your Next.js site without hurting Largest Contentful Paint, hydration, or SEO. This is the five-minute version.

The script tag, but lazy

The classic embed is a script tag before </body>. In Next.js you use next/script with strategy="lazyOnload" so the widget is fetched after your page is interactive:

// app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://your-anserra-instance.com/widget.js"
          data-anserra-key="YOUR_EMBED_KEY"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}

Why lazyOnload? It’s the least-priority strategy Next.js offers. The widget won’t run until the browser is idle. Your LCP stays clean.

Why not import a React component

Some chatbot vendors ship a React component you import. This is worse for Next.js than a script tag:

  • The component bundle counts toward your initial JavaScript payload.
  • Server components can’t render it — you need a client boundary.
  • The chatbot’s dependencies (React, framer-motion, etc.) get bundled into your app if you don’t tree-shake carefully.

The script tag pattern isolates the widget entirely. It runs in its own scope, doesn’t collide with your bundle, and can be removed by editing one line.

Route-restricted embedding

If you only want the chatbot on some pages, put <Script> in the route’s layout, not the root layout:

// app/support/layout.tsx
import Script from "next/script";

export default function SupportLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <Script src="..." data-anserra-key="..." strategy="lazyOnload" />
    </>
  );
}

Now the widget loads only on /support/* routes.

Programmatic control

The Anserra widget exposes a global window.Anserra once the script has loaded. If you want to open it from a “Chat with support” button:

"use client";

import { useEffect } from "react";

export function ChatButton() {
  useEffect(() => {
    // Ensures the widget is initialized before we touch it.
  }, []);

  return (
    <button onClick={() => window.Anserra?.open()}>
      Chat with support
    </button>
  );
}

To attach known-user context so leads carry the visitor’s identity:

"use client";
import { useEffect } from "react";
import { useSession } from "next-auth/react";

export function AnserraVisitor() {
  const { data } = useSession();
  useEffect(() => {
    if (data?.user) {
      window.Anserra?.setVisitor({
        name: data.user.name,
        email: data.user.email,
      });
    }
  }, [data]);
  return null;
}

Mount <AnserraVisitor /> once, near the root of your app, inside a client boundary.

The mobile trap

By default, a floating chatbot at position: fixed; bottom: 0 can cover the last row of a mobile page — including your buy button. On mobile viewports we automatically shrink the launcher, but if you use env(safe-area-inset-bottom) and a bottom-fixed CTA, respect it explicitly:

.buy-button {
  position: sticky;
  bottom: calc(env(safe-area-inset-bottom) + 80px); /* leaves room for the launcher */
}

What breaks in App Router

The one thing that catches teams: the widget script depends on window. If you accidentally import it from a server component, the build fails. Always mount it via next/script (which is already client-scoped) or inside a component annotated with "use client".

The one-line summary

<Script src="https://.../widget.js" data-anserra-key="..." strategy="lazyOnload" />

That’s it. Get an embed key and paste it in.

Try Anserra

Custom AI chatbots trained on your content — grounded, cited, embedded anywhere.

Read next

  • How to train a chatbot on your help centre (2026 guide)

    Step-by-step: turn a public help centre into a chatbot that deflects tickets. Crawl configuration, chunking, testing and the failure modes to expect.

    Read
  • Hybrid retrieval, explained: why semantic search alone fails on real customer questions

    Semantic embeddings blur exact strings like SKUs and error codes. Hybrid retrieval fixes that — here's how it works and how we implement it.

    Read
  • pgvector vs Pinecone in 2026: when each wins

    A pragmatic comparison of Postgres pgvector against Pinecone for production RAG. Pricing, latency, filtering, hybrid search and what breaks under load.

    Read