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.
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.
ReadHybrid 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.
Readpgvector 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