Skip to main content
First 6 months free

How to Add an AI Chatbot to a React or Next.js Website

Adding an AI chatbot for React or Next.js sites should take an afternoon, not a sprint. This guide walks the three integration paths, script embed, npm component and headless API, and the SSR and performance traps that break naive installs.

12 min readUpdated Engineering
Try EzyConn Free

The 30-second answer

The fastest path is a script embed with next/script and strategy="lazyOnload" in your root layout, live in about 10 minutes with zero LCP impact. Need per-route control or design-system theming? Wrap the widget in a client component loaded via dynamic(..., { ssr: false }). Building an in-product assistant? Use a headless chat API and own the UI, budget 1-3 days. Whatever you pick, keep the widget off the critical rendering path: deferred correctly, it adds under 50 ms of post-idle main-thread work.

Three ways to add an AI chatbot to React

Every AI chatbot for React integration falls into one of three buckets, and picking the wrong one is the most common source of wasted engineering time. Teams routinely spend two days building a custom chat UI when a 10-minute embed would have shipped the same business outcome, or, in the opposite failure, try to bend a hosted widget into an in-product copilot it was never designed to be.

Integration path
Setup time
Best for
Script embed via next/script
5-15 minutes
Marketing sites, docs, dashboards, 90% of use cases
npm component + dynamic import
30-60 minutes
Per-route control, props-driven config, design-system theming
Headless API, custom UI
1-3 days
In-product assistants, doc copilots, fully bespoke interfaces

If you are still comparing vendors before wiring anything up, start with our rundown of the best AI chatbot platforms in 2026, the integration mechanics below apply to virtually all of them.

Path 1: script embed with next/script

Every hosted chatbot ships a one-line script tag. In Next.js, never paste it raw into JSX, use the next/script component so the framework controls when it loads. Put it in your root layout so the widget persists across client-side navigations:

// app/layout.tsx (App Router)
import Script from 'next/script'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://cdn.ezyconn.com/widget.js"
          data-app-id="YOUR_APP_ID"
          strategy="lazyOnload"
        />
      </body>
    </html>
  )
}

The strategy matters more than anything else on this page. afterInteractive (the default) runs the script right after hydration, which competes with your own code for main-thread time. lazyOnload waits for the browser's idle period after the load event, the right choice for a support widget nobody clicks in the first two seconds. Avoid beforeInteractive entirely; a chatbot has no business blocking hydration.

On the Pages Router, the same Script tag goes in pages/_app.tsx, not _document.tsx, next/script is not supported in _document, and _app guarantees a single mount across every route. This path is functionally identical to adding live chat to any website, just with framework-aware loading.

Path 2: npm component with dynamic import

Some platforms ship a React component instead of (or alongside) a script tag. Components give you props-driven configuration, theme tokens, locale, user identity, per-route visibility, but they introduce the classic SSR trap: chat SDKs reference window at import time and crash the server build. The fix is to opt the component out of server rendering:

'use client'
import dynamic from 'next/dynamic'

const ChatWidget = dynamic(
  () => import('@/components/ChatWidget'),
  { ssr: false }
)

export function SupportLauncher() {
  return <ChatWidget theme="dark" locale="en" />
}

Note the 'use client' directive: since Next.js 15, ssr: false is only allowed inside client components in the App Router, using it in a server component throws at build time. On the Pages Router, dynamic(..., { ssr: false }) works anywhere. Budget 30-60 minutes for this path, most of it spent mapping your design tokens to the widget's theme props.

Path 3: headless API for a fully custom UI

The headless path skips the widget entirely: your React components call a chat API (usually a streaming endpoint), and you render messages however you like. This is the right call for in-product assistants, docs copilots and any UI where a floating bubble feels wrong. It is also 10-20x the effort, you now own streaming token rendering, optimistic updates, retry and error states, conversation persistence, rate limiting and keyboard accessibility.

  • Proxy through your own API route. Never call the AI provider from the browser with a raw key. A Next.js route handler at app/api/chat/route.ts keeps credentials server-side and lets you enforce auth and per-user rate limits.
  • Stream responses. Users perceive a streaming answer that starts in 300 ms as dramatically faster than a complete answer that arrives in 3 seconds. Use server-sent events or a ReadableStream from the route handler.
  • Ground the model in your content. A custom UI with an ungrounded model hallucinates. Retrieval over your real docs is non-negotiable, see our guide to training an AI chatbot on your website.

SSR and hydration gotchas (and their fixes)

Five failure modes account for nearly every "the chatbot broke my Next.js build" ticket. All five have two-line fixes once you know them:

"window is not defined" at build time

Build failure

Widget SDKs often touch window or document at import time, which crashes server rendering. Fix: load via next/script (never imported on the server), or wrap the component in a dynamic import with ssr: false so the module is only evaluated in the browser.

Hydration mismatch warnings

Console errors, UI flicker

If the server renders nothing but the client renders a launcher, React logs a mismatch. Fix: render null until a mounted flag flips in useEffect, so server and first client render agree, then mount the widget.

Double widget in development

Two launchers

React 18 StrictMode runs effects twice in dev. If your effect injects a script tag, you get two. Guard with a ref or check document.querySelector for the script src before injecting. next/script dedupes by src automatically.

Widget unmounts on route change

Lost conversations

Mount the embed once in the root layout (App Router) or _app.tsx (Pages Router), not inside a page component. SPA navigation then preserves the open conversation instead of resetting it on every click.

Content Security Policy blocks the script

Silent failure

Strict CSP headers need the vendor added to script-src and its API to connect-src. Check the browser console for CSP violations first whenever the widget simply never appears in production.

The CSP point deserves emphasis for anyone shipping to regulated customers: a widget that phones home to a third-party API is part of your security surface. Our AI chatbot security best practices guide covers the headers, data-handling and audit questions to clear before launch.

Performance: keep the chatbot off the critical path

A chat widget is the textbook third-party script: 100-350 KB compressed, plus fonts and iframe documents once opened. Loaded eagerly on a mid-tier Android device, that can mean 200-600 ms of extra main-thread blocking, enough to fail the 200 ms INP threshold on an otherwise fast site. Loaded lazily, the same widget is effectively free. Three escalating strategies:

  • Level 1, lazyOnload (10 minutes): the next/script strategy above. Widget loads during idle time after the page finishes. Good enough for most sites.
  • Level 2, load on intent (30 minutes): attach one-time listeners for scroll, pointermove or touchstart and inject the script on the first signal. The widget exists only for humans who interact, bots and bounces never pay the cost.
  • Level 3, facade (1-2 hours): render a static launcher button in plain JSX (~1 KB) and load the real widget only on click, showing a brief loading state. This is the pattern Lighthouse itself recommends for chat embeds and yields a true zero-cost idle state.

Verify with a before/after Lighthouse run: LCP should be identical, and total blocking time should move by less than 50 ms. While you are measuring, remember the business side of the equation, widget cost is measured in milliseconds, but widget value is measured in deflected tickets and captured leads. The math in our chatbot ROI calculator guide shows why a correctly deferred widget is one of the highest-use scripts you can ship.

App Router vs Pages Router: a quick reference

  • App Router: put the Script in app/layout.tsx; the root layout never remounts on navigation, so conversations persist. ssr: false dynamic imports must live in client components.
  • Pages Router: put the Script in pages/_app.tsx; ssr: false works in any component.
  • Both: next/script dedupes by src, so an accidental second include is harmless; identify logged-in users via the widget's identity API in a useEffect, never during render; and gate the widget by route with usePathname() if you want it hidden on checkout or auth pages.

Frequently Asked Questions

Can I add a chatbot to a plain React SPA without Next.js?

Yes, paste the script into index.html before the closing body tag, or inject it from a guarded useEffect. No SSR means no SSR gotchas; just defer loading and guard against StrictMode double-injection in dev.

Will a chat widget hurt Core Web Vitals?

Not if deferred. With lazyOnload, LCP is unchanged and blocking time rises under 50 ms. Loaded eagerly, a 100-350 KB widget can add 200-600 ms of main-thread work on mid-tier phones and push INP past 200 ms.

Should I build a custom UI with the headless API?

Only for product-embedded assistants. The API path costs 1-3 days plus ongoing maintenance versus 15 minutes for the embed; theme overrides on a hosted widget cover most design requirements.

How does the bot learn my content?

Platforms like EzyConn crawl your pages and docs and build a retrieval index, usually live in under 30 minutes, with scheduled re-crawls keeping answers current as you ship.

Ship a chatbot, not a regression

EzyConn drops into React and Next.js with one lazy-loaded script, trains on your site in minutes, and stays off your critical rendering path. Free to start, no credit card.

Start Free

Last updated . Performance figures reflect Lighthouse testing on mid-tier mobile hardware and typical widget bundle sizes as of mid-2026. View more guides.

Related resources