# Install the chat widget in Next.js and React

> Add the intoCHAT widget to a Next.js app with next/script in the root layout, or to a React app with index.html or a useEffect hook, without duplicate bubbles.

The intoCHAT widget is a plain script, not a React component. It reads your agent ID from its `data-chatbot-id` attribute and adds the launcher to `document.body`, outside your React tree, so re-renders and route changes don't touch it.

## Next.js App Router

Load the script once in the root layout with the `Script` component from `next/script`:

```tsx
import Script from 'next/script'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://www.intochat.ai/api/embed.js"
          data-chatbot-id="YOUR_AGENT_ID"
          strategy="lazyOnload"
        />
      </body>
    </html>
  )
}
```

- **The data attribute.** Next.js forwards extra attributes such as `data-chatbot-id` to the final script element, which is where the widget looks for it.
- **The strategy.** `lazyOnload` loads the widget during browser idle time, after the page's other resources. `afterInteractive`, the default, loads it earlier, so the launcher appears sooner. Both work.
- **Not `beforeInteractive`.** That strategy is for scripts the page needs before it becomes interactive. The chat widget isn't one, and loading it that early only competes with your own code.
- **Server Components.** The layout can stay a Server Component, because this `Script` has no event handlers.

Next.js loads the script only once, even as visitors move between routes. In the Pages Router, add the same `Script` to `pages/_app` instead.

To show the chat under some routes only, put the `Script` in a nested layout. Once loaded, the launcher stays on the page after client-side navigation to other routes, until the next full page load.

## React with Vite or Create React App

Choose one of two options, not both. The embed script ignores a second copy for the same agent, so using both only loads it twice.

**Option 1: index.html.** Paste the script tag from the **Share** tab just before `</body>` in `index.html`: in the project root for Vite, or in `public/index.html` for Create React App.

**Option 2: a component.** Use this if you want to decide in code whether the widget loads, for example only in production:

```jsx
import { useEffect } from 'react'

export default function IntoChatWidget() {
  useEffect(() => {
    // Load the widget only once, even when StrictMode runs effects twice
    if (document.getElementById('intochat-embed')) return

    const script = document.createElement('script')
    script.id = 'intochat-embed'
    script.src = 'https://www.intochat.ai/api/embed.js'
    script.setAttribute('data-chatbot-id', 'YOUR_AGENT_ID')
    document.body.appendChild(script)
  }, [])

  return null
}
```

Render `IntoChatWidget` once, in your root component such as `App`. Set the attribute before the element is added to the page, as above.

### StrictMode and duplicate launchers

In development, React StrictMode runs effects twice. The `id` check at the top of the effect keeps the script from being added twice. Even without it you wouldn't get a second launcher, because the embed script adds only one per agent and page. Don't remove the script in a cleanup function: removing it doesn't remove the launcher, which stays in `document.body` until the next full page load.

## Client-side actions

The agent can call functions in your app during a chat, for example to read state or open a form. Register them with `window.IntoChatActions.register`. See [Client-side actions](/en/docs/client-actions).

## Content Security Policy

Allow `https://www.intochat.ai` in `script-src`, `connect-src` and `frame-src`. If your policy uses nonces, pass the `nonce` to the `Script` component. Next.js forwards it like the data attribute.

## Check it

Open any route. The launcher appears in the bottom corner and stays as you navigate. Two launchers mean two snippets with different agent IDs are loaded. No launcher and "Chatbot not found." in the console means the agent ID is wrong. See [Troubleshooting](/en/docs/troubleshooting) for other cases, and the [Next.js](/en/integrations/nextjs) and [React](/en/integrations/react) guides for more background.
