How to add an AI chatbot to a Next.js app

In the App Router, add <Script src="https://w.feedbotai.com/v1/feedbot.js" data-bot-id="..." strategy="lazyOnload" /> to app/layout.tsx, or render <FeedbotWidget /> from @feedbotai/react there. To identify signed-in users, compute an HMAC hash of the user id on the server and pass it as userHash.

  • Setup: 5 minutes
  • Free plan, no card
  • Updated

Most “nextjs chatbot” results are templates for building your own chat app, such as the Vercel AI chatbot template. Use one if the chat is your product. If you need a support or sales assistant on an existing Next.js site, embedding is faster. Here’s how to do it with Feedbot (we make it).

What you need

  • A Feedbot bot and its bot id from Dashboard → Bot → Install. The Free plan is enough to test.
  • Next.js 13 or later. Both routers are covered below.

App Router: next/script

Add the script to the root layout:

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://w.feedbotai.com/v1/feedbot.js"
data-bot-id="YOUR_BOT_ID"
strategy="lazyOnload"
/>
</body>
</html>
);
}

Which strategy to use, per the next/script reference:

Strategy When it loads Use for the chatbot?
lazyOnload Browser idle time, after all page resources Yes. Recommended.
afterInteractive (default) After some hydration Fine if you want the button sooner.
beforeInteractive Before any Next.js code No. Meant for critical scripts like consent managers.
worker In a web worker (experimental) No. Doesn’t work with the App Router.

The loader itself is about 6 KB gzipped and fetches the chat only on hover, click or idle, so lazyOnload costs nothing noticeable.

App Router: @feedbotai/react

The package wraps the same loader in a component and adds a hook.

Terminal window
pnpm add @feedbotai/react
app/layout.tsx
import { FeedbotWidget } from "@feedbotai/react";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<FeedbotWidget botId={process.env.NEXT_PUBLIC_FEEDBOT_BOT_ID!} />
</body>
</html>
);
}

FeedbotWidget is a client component, so it’s safe to render from this server component. To open the chat from your own button, use the hook in a client component:

components/help-button.tsx
"use client";
import { useFeedbot } from "@feedbotai/react";
export function HelpButton() {
const { sendMessage } = useFeedbot();
return <button onClick={() => sendMessage("How do I upgrade?")}>Ask about upgrading</button>;
}

Pages Router

Put the script in pages/_app.tsx so it loads on every page:

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://w.feedbotai.com/v1/feedbot.js"
data-bot-id="YOUR_BOT_ID"
strategy="lazyOnload"
/>
</>
);
}

<FeedbotWidget> works in _app.tsx the same way.

Identify signed-in users with a server-side hash

  1. Copy the secret from Bot → Install → Identity verification into FEEDBOT_IDENTITY_SECRET. No NEXT_PUBLIC_ prefix: it must stay on the server.
  2. Compute the hash and pass it with the user:
app/layout.tsx
import { createHmac } from "node:crypto";
import { FeedbotWidget } from "@feedbotai/react";
import { getCurrentUser } from "@/lib/auth"; // your auth
function feedbotHash(userId: string) {
return createHmac("sha256", process.env.FEEDBOT_IDENTITY_SECRET!).update(userId).digest("hex");
}
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const user = await getCurrentUser(); // null when logged out
return (
<html lang="en">
<body>
{children}
<FeedbotWidget
botId={process.env.NEXT_PUBLIC_FEEDBOT_BOT_ID!}
user={user ? { id: user.id, email: user.email, name: user.name } : null}
userHash={user ? feedbotHash(user.id) : undefined}
metadata={user ? { plan: user.plan } : undefined}
/>
</body>
</html>
);
}

When user turns into null on logout, the component calls reset, so the next person on that device starts fresh.

One trade-off: reading the session (cookies) in the root layout makes every route dynamic. If you want marketing pages to stay static, put this in the layout of your signed-in area only (for example app/(app)/layout.tsx), or return { user, userHash } from a route handler and fetch it in a client component. In the Pages Router, compute the hash in getServerSideProps or an API route. Details in Identify users.

Show the chat on some pages only

Place the <Script> or <FeedbotWidget> in a nested layout instead of the root one, for example app/(marketing)/layout.tsx or app/(app)/layout.tsx. With lazyOnload or afterInteractive, the script loads only when a page under that layout is opened.

Troubleshooting

Symptom Cause Fix
No button, console says data-bot-id is required Env variable undefined in the browser Client-side variables need the NEXT_PUBLIC_ prefix. Rebuild after changing it.
Chat button appears late lazyOnload waits for idle time Expected. Switch to afterInteractive if you want it sooner.
Requests rejected on preview deployments Preview domain not in Allowed domains Add the preview hostname. Don’t add vercel.app itself: subdomains of a listed domain are allowed, so that would open your bot to every Vercel site.
Blocked by CSP Strict Content-Security-Policy Allow https://w.feedbotai.com in script-src and https://api.feedbotai.com in connect-src.
User unverified Hash of a different value, or old secret Hash exactly the id you pass, with the current secret. Redeploy after Rotate.

Train it on your docs

The Free plan learns from text and FAQ. From Starter, import your site or docs by URL or sitemap.xml (Next.js apps that render content client-side are read in a real browser) and upload files. See knowledge base.

Next steps

Questions

Can't find an answer? Ask the bot in the corner, or write to us.

Which next/script strategy should I use for a chatbot?

Use lazyOnload. The Next.js docs list chat support plugins as a typical lazyOnload script: it loads during browser idle time, after the rest of the page, so it doesn't compete with your own code.

Does @feedbotai/react work with the App Router and server components?

Yes. FeedbotWidget ships as a client component, so you can render it from a server component such as app/layout.tsx. It renders null on the server and touches window only in effects.

Where do I compute the userHash?

On the server: in a server component, a route handler or getServerSideProps. The hash is HMAC-SHA256 of the user id with your identity secret. Never expose the secret with a NEXT_PUBLIC_ variable.

Does the chat reload on client-side navigation?

No. Scripts in the root layout run once per document load. Navigating between pages with next/link keeps the widget and the open conversation.

Is this a Next.js AI chatbot template?

No. Templates like the Vercel AI chatbot give you source code to build and host your own chat app. Feedbot is a hosted assistant you embed: chat UI, knowledge base, lead capture and human handoff are already built.

Keep exploring

Put one agent on your site today

Start on the free plan. Upgrade when conversations grow.