✨ Train your first AI chatbot free — no credit card neededStart free →
Alee
← All resources
Integrations · 13 min read

Add an AI Chatbot to a Next.js App

Add an AI chatbot to your Next.js app the right way: embed script, App Router, RAG, streaming, and lead capture explained step by step.

Most "add a chatbot to your site" tutorials assume a static WordPress page where you paste one <script> tag and walk away. Next.js breaks that assumption almost immediately. Between the App Router, server components, hydration boundaries, streaming responses, and a strict Content Security Policy, dropping an AI chatbot for Next.js in the wrong place can blow up your build, double-render the widget, or silently get blocked by the browser. The good news: once you understand where Next.js wants third-party scripts to live and how to wire a chat backend without leaking your API keys, a Next.js chatbot becomes a clean, predictable integration you can ship in an afternoon.

This guide walks through both paths people actually take. The first is embedding a hosted, no-code AI chatbot (the fastest route, and the one most teams should start with). The second is building a custom chat route inside your own Next.js app with the App Router, streaming, and your own retrieval layer. We'll cover the App Router and the Pages Router, server vs. client components, CSP gotchas, and how to make the bot actually useful by training it on your own content instead of letting it hallucinate. By the end you'll know exactly where to put the code, what to avoid, and how to capture leads instead of just answering trivia.

Two ways to add an AI chatbot to Next.js

Before writing a line of code, decide which of these you're building. They have completely different effort curves.

Option 1: Embed a hosted chatbot widget

You sign up for a platform, point it at your website or upload your docs, and it gives you a small embed snippet. The platform handles the language model, retrieval, conversation memory, rate limiting, and the chat UI. Your only job in Next.js is to load one script in the right place.

This is the right call when:

  • You want the bot live this week, not next quarter.
  • Your content (docs, help center, product pages, PDFs) is the source of truth and you want answers grounded in it.
  • You don't want to own model costs, prompt engineering, vector databases, or moderation.
  • You need lead capture, analytics, and human handoff out of the box.

Tools in this space include Alee, Intercom's Fin, Chatbase, and SiteGPT. Alee is built specifically for the white-label, train-on-your-own-content use case: you feed it your site and documents, it builds a retrieval-augmented bot, and you embed it under your own brand. If you're weighing options, our roundup of the best SiteGPT alternatives compares the trade-offs honestly.

Option 2: Build a custom chat route in your app

You write your own API route (/api/chat), call a model provider directly, manage your own retrieval, and render your own chat component. This gives you total control over UX, data flow, and cost — at the price of owning everything: streaming, error states, rate limiting, prompt injection defense, and keeping the knowledge base fresh.

This is the right call when:

  • The chatbot is a core product feature, not a support add-on.
  • You have unusual UX requirements (inline citations into your app, custom tool calls, deep integration with your auth and database).
  • You have engineering time to maintain it.

Most teams overestimate how much they need Option 2. A surprising number of "we'll build it ourselves" projects end up half-finished because the hard part was never the chat bubble — it was the retrieval quality and content freshness. We'll cover both, starting with the fast path.

The fast path: embed an AI chatbot for Next.js

Here's the thing that trips people up. In a plain HTML site, you paste the embed snippet right before </body>. In Next.js, there is no </body> you edit per-page — the framework controls the document. So you need to use the right primitive.

Use the next/script component, not a raw script tag

Next.js ships a <Script> component that controls when and how third-party scripts load, so they don't block your page or fight with hydration. Always prefer it over a raw <script> tag.

In the App Router, add the widget to your root layout so it loads on every route:

```tsx
// 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://cdn.aleeup.com/widget.js"
data-bot-id="YOURBOTID"
strategy="afterInteractive"
/>
</body>
</html>
);
}
```

The strategy prop is the part most people get wrong:

  • `afterInteractive` (the default and the right choice for chat widgets) loads the script after the page becomes interactive. The chat bubble appears a beat after content paints, which is exactly what you want — your content is never blocked by the widget.
  • `lazyOnload` waits until the browser is idle. Use this if the bubble is genuinely low priority and you care about squeezing every millisecond of initial load.
  • `beforeInteractive` loads before hydration and can only be used in the root layout. Don't use it for a chat widget — there's no reason to prioritize a support bubble over your actual page.

Pages Router: load it once in _app or via the Script component

If you're still on the Pages Router, you have two clean options. Put the <Script> in pages/_app.tsx so it loads across the whole app:

```tsx
// pages/_app.tsx
import type { AppProps } from "next/app";
import Script from "next/script";

export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<Script
src="https://cdn.aleeup.com/widget.js"
data-bot-id="YOURBOTID"
strategy="afterInteractive"
/>
</>
);
}
```

Avoid putting the embed in pages/_document.tsx with a raw <script>. _document is for the HTML shell, runs only on the server, and using it for interactive scripts is a common source of "the widget loads twice" and "the widget doesn't load at all" bugs.

One widget, not one per page

The single most common mistake is dropping the embed into an individual page component (say, app/pricing/page.tsx) and then again into the layout. On client-side navigation, Next.js doesn't do a full page reload, so you can end up with multiple script instances or multiple chat bubbles stacked on top of each other. Rule of thumb: load the widget exactly once, as high in the tree as makes sense (root layout or _app), and let it persist across route changes.

For a deeper walkthrough of embedding patterns across different stacks, see our guide on how to embed an AI chatbot on your website.

Make the widget play nice with your Content Security Policy

If your Next.js app sets a strict CSP (a good practice), the browser will silently block the widget unless you allowlist it. You'll see console errors like "Refused to load the script because it violates the following Content Security Policy directive."

In next.config.js, add the widget's domains to the relevant directives:

```js
// next.config.js
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.aleeup.com;
connect-src 'self' https://api.aleeup.com;
frame-src https://cdn.aleeup.com;
img-src 'self' data: https://cdn.aleeup.com;
`;

module.exports = {
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Content-Security-Policy",
value: cspHeader.replace(/\n/g, ""),
},
],
},
];
},
};
```

The exact domains depend on your provider — check their docs for the script CDN, the API host the chat calls (connect-src), and any iframe host (frame-src). Test in an incognito window with the console open; CSP violations are loud once you know to look for them.

Verify it actually loaded

After deploying, confirm three things:

  • The chat bubble renders on multiple routes, including after a client-side navigation (click an internal link, don't refresh).
  • No CSP or CORS errors in the browser console.
  • A test question gets a grounded answer, not a generic "I'm an AI" response — which means your content was actually ingested.

That's the entire fast path. For most marketing sites, docs portals, and SaaS dashboards, you're done here.

The custom path: build a Next.js chatbot with the App Router

If you genuinely need a custom Next.js chatbot, the App Router gives you a clean place to put the backend: a Route Handler. The cardinal rule is that your model provider API key lives only on the server. Never ship it to the client, never inline it in a component, never prefix it with NEXT_PUBLIC_.

Create a streaming chat route handler

Create app/api/chat/route.ts. This runs server-side, reads your secret key from the environment, calls the model, and streams tokens back so the UI feels responsive instead of freezing for ten seconds.

```ts
// app/api/chat/route.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
apiKey: process.env.ANTHROPICAPIKEY, // server-only, never NEXTPUBLIC
});

export const runtime = "edge"; // optional: lower latency for streaming

export async function POST(req: Request) {
const { messages } = await req.json();

const stream = await client.messages.stream({
model: "claude-sonnet-4-5",
max_tokens: 1024,
system:
"You answer questions using ONLY the provided context. " +
"If the answer isn't in the context, say you don't know and offer to connect a human.",
messages,
});

return new Response(stream.toReadableStream(), {
headers: { "Content-Type": "text/event-stream" },
});
}
```

A few things worth calling out:

  • `runtime = "edge"` can reduce time-to-first-token, but edge runtimes don't support every Node API. If you need the Node runtime (for certain database drivers or SDK features), drop that line and the route runs as a normal serverless function.
  • The system prompt is doing real work. Telling the model to answer only from provided context and to admit ignorance is the difference between a helpful bot and a confident liar.
  • You'll want rate limiting and input validation here too. A public /api/chat route with no limits is an open invitation to run up your model bill.

Build the client chat component

The chat UI is a Client Component ("use client") because it needs state and event handlers. Keep it lean — it sends messages to your route and renders the streamed response.

```tsx
// app/components/Chat.tsx
"use client";

import { useState } from "react";

export default function Chat() {
const [input, setInput] = useState("");
const [messages, setMessages] = useState<
{ role: string; content: string }[]
>([]);

async function send() {
const next = [...messages, { role: "user", content: input }];
setMessages(next);
setInput("");

const res = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ messages: next }),
});

const reader = res.body!.getReader();
const decoder = new TextDecoder();
let assistant = "";

while (true) {
const { done, value } = await reader.read();
if (done) break;
assistant += decoder.decode(value);
setMessages([...next, { role: "assistant", content: assistant }]);
}
}

return (
<div>
{messages.map((m, i) => (
<p key={i}>
<strong>{m.role}:</strong> {m.content}
</p>
))}
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={send}>Send</button>
</div>
);
}
```

This is deliberately bare-bones. Production versions add abort controllers, error boundaries, optimistic UI, markdown rendering, and a typing indicator. But the architecture is the point: server route holds the key, client component holds the UI.

Don't skip retrieval, or your bot will make things up

A raw model call answers from its training data, which knows nothing about your pricing, your refund policy, or your product's current feature set. That's where retrieval-augmented generation (RAG) comes in: before calling the model, you fetch the most relevant chunks of your own content and pass them as context.

The pipeline looks like this:

  1. Ingest your content (site pages, docs, PDFs, help articles) and split it into chunks.
  2. Embed each chunk into a vector and store it in a vector database (Pinecone, pgvector, and others).
  3. At query time, embed the user's question, find the closest chunks, and inject them into the system prompt as context.
  4. Generate an answer grounded in those chunks, ideally with citations.

This is the part teams underestimate. Good retrieval means chunking sensibly, keeping embeddings fresh as content changes, handling follow-up questions, and tuning how many chunks you retrieve. If you want the conceptual grounding before you build, our explainer on what RAG is and how it works breaks it down without the jargon.

This is also the strongest argument for the hosted path. Platforms like Alee do the entire ingest → embed → retrieve → generate loop for you, re-crawl your site on a schedule, and keep the bot in sync — so you're not hand-maintaining a vector pipeline while also building product.

Performance, SEO, and Core Web Vitals

A chatbot is a third-party script, and third-party scripts are where page performance goes to die if you're careless. Next.js gives you the tools to avoid that.

Keep the widget off the critical path

  • Use strategy="afterInteractive" or lazyOnload so the widget never blocks first paint.
  • A hosted widget should load its heavy assets after your content. If a vendor's snippet forces a render-blocking script, that's a red flag.
  • The chat bubble itself should be a tiny launcher; the full chat UI should only load when a user opens it. Most reputable widgets do this already.

The widget shouldn't hurt your indexing

Search engines render JavaScript, but you don't want a chat widget injecting content that confuses crawlers or shifts layout. Two safeguards:

  • Make sure the widget doesn't cause layout shift (CLS). It should overlay your content in a fixed position, not push it around. A floating bubble in the bottom corner is the safe default.
  • Don't rely on the chatbot to deliver content that should be in your actual HTML for SEO. The bot is an assistance layer, not a substitute for crawlable pages.

Measure it

Run Lighthouse before and after adding the widget. Watch Largest Contentful Paint and Total Blocking Time specifically. If the widget moves those numbers meaningfully, revisit your strategy or talk to the vendor. A well-built chat widget should be nearly invisible in your performance metrics.

Turn the chatbot into a lead engine, not just an FAQ

Answering questions is table stakes. The reason to put a bot on a Next.js app — especially a marketing site or SaaS landing page — is usually to convert traffic. A bot that resolves a visitor's question and then quietly captures their email is doing two jobs at once.

Practical patterns that work:

  • Qualify, then capture. When a visitor asks about pricing or a use case, the bot can answer and then offer a demo or a callback, collecting name and email in the flow.
  • Route by intent. Pre-sales questions go to a "book a demo" path; support questions go to a help path; everything else to human handoff.
  • Pass leads to your stack. A good platform forwards captured leads to your CRM or via webhook, so your Next.js app doesn't have to be the system of record.

If conversion is the goal, our guide to lead generation with chatbots covers the conversation design patterns that actually move the needle, and our piece on chatbot best practices covers tone, fallbacks, and when to get out of the user's way.

Watch the right metrics

Don't measure a chatbot by message volume. Measure:

  • Resolution rate — what share of conversations end without needing a human.
  • Lead capture rate — conversations that produced a qualified contact.
  • Deflection — support tickets avoided.
  • Containment vs. escalation — how often the bot correctly hands off.

These tell you whether the bot is earning its place. Vanity metrics like "messages sent" don't.

A note on regulated industries

If your Next.js app serves a bank, insurer, clinic, law firm, or any finance- or health-adjacent business, scope the bot deliberately. Configure it to handle logistics and FAQs only — hours, locations, how to start a claim, what documents to bring, how to book an appointment, where to find a form. The bot should not provide medical, legal, or financial advice, and you should say so explicitly in the system prompt and the UI.

Three guardrails are non-negotiable here:

  • Explicit human handoff. The moment a question crosses into advice, diagnosis, or a specific account decision, the bot should hand off to a qualified human, not improvise.
  • Clear disclaimers. State plainly that the assistant provides general information only and is not a substitute for professional advice.
  • Strict grounding. Answer only from approved content. A hosted, RAG-based bot trained on your vetted material is far safer than a free-form model that might confidently invent a policy.

Done right, the bot handles the high-volume logistical questions that clog your phone lines while a human owns anything that carries real consequences.

Hosted or custom: how to actually decide

Here's the honest framing. Choose the custom App Router build if the chatbot is a differentiated product feature, you have engineers who'll maintain retrieval and content freshness, and you need deep integration with your own data and tools.

Choose the hosted embed if you want a grounded, on-brand bot live quickly, you'd rather not own a vector pipeline and model costs, and you need lead capture, analytics, and handoff without building them. For most marketing sites, docs, and support use cases, this is the pragmatic winner — and you can always graduate to a custom build later once you know what your users actually ask.

If you go hosted, Alee is worth a look: you point it at your site, it trains on your content, and you embed one script in your Next.js layout. To understand what's happening under the hood before you commit, read how to build an AI chatbot trained on your website.

Frequently asked questions

Where exactly do I put the chatbot script in a Next.js app?

Load it once, as high in the component tree as possible. In the App Router, put a next/script <Script> tag in app/layout.tsx so it persists across all routes. In the Pages Router, put it in pages/_app.tsx. Avoid dropping it into individual page components, which causes duplicate widgets on client-side navigation, and avoid _document.tsx for interactive scripts.

Will an AI chatbot slow down my Next.js site?

It shouldn't, if you load it correctly. Use strategy="afterInteractive" or lazyOnload on the next/script component so the widget loads after your content paints and never blocks the critical path. A well-built widget loads only a small launcher upfront and fetches the full chat UI when a user opens it. Run Lighthouse before and after to confirm Largest Contentful Paint and Total Blocking Time aren't affected.

Do I need to build my own RAG pipeline?

Only if you're going the fully custom route. RAG — retrieving relevant chunks of your own content and feeding them to the model — is what stops the bot from making things up, but building and maintaining the ingest, embedding, and retrieval layers is real ongoing work. Hosted platforms like Alee handle the entire pipeline and keep it synced as your content changes, which is why most teams start there.

How do I keep my API key safe in a Next.js chatbot?

Never expose a model provider key to the browser. In a custom build, the key lives in a server-side Route Handler (app/api/chat/route.ts) and is read from an environment variable without the NEXT_PUBLIC_ prefix, so it never ships to the client. The client component talks only to your own /api/chat route, which calls the model on the server. With a hosted widget, the platform manages keys for you and the embed never carries a secret.

Can the chatbot capture leads and not just answer questions?

Yes, and that's often the main reason to add one. The bot can answer a visitor's question, then offer a demo or callback and collect their name and email in the same conversation, forwarding qualified leads to your CRM or a webhook. Measure it by lead capture rate and resolution rate rather than raw message count to know whether it's actually earning its place.

Is a hosted widget or a custom build better for SEO?

Both are fine for SEO as long as the chat content isn't load-bearing for crawlers. Keep anything that should be indexed in your real, crawlable HTML, and treat the chatbot as an assistance layer on top. Ensure the widget is a fixed-position overlay so it doesn't cause layout shift, and load it after interactive so it doesn't affect Core Web Vitals.

Ready to add a grounded, on-brand AI chatbot to your Next.js app without building a retrieval pipeline from scratch? Train Alee on your own content, embed one script in your layout, and start capturing leads while it answers visitors in your brand voice. Start free and have your Next.js chatbot live today.

Build your own AI chatbot with Alee

Train it on your site, embed it anywhere, capture leads 24/7. Free to start.

Related reading