> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usefrictionless.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js and React

> Load the chat once in a layout that stays mounted, with types for window.Frictionless.

## Next.js

Don't paste the raw tags into a component: `init()` can run before the script has loaded. Use `next/script` and put the component in your root layout, which stays mounted as visitors move between pages.

<CodeGroup>
  ```tsx app/frictionless-chat.tsx theme={null}
  "use client";

  import Script from "next/script";

  export default function FrictionlessChat() {
    return (
      <Script
        src="https://usefrictionless.com/widget/v1.js"
        strategy="afterInteractive"
        onReady={() => window.Frictionless.init({ company_id: process.env.NEXT_PUBLIC_FRICTIONLESS_WORKSPACE_ID! })}
      />
    );
  }
  ```

  ```tsx app/layout.tsx theme={null}
  import FrictionlessChat from "./frictionless-chat";

  export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
      <html lang="en">
        <body>
          {children}
          <FrictionlessChat />
        </body>
      </html>
    );
  }
  ```
</CodeGroup>

Calling `init()` twice is safe: the second call is ignored, so a remount never adds a second chat.

## React (Vite and others)

```tsx useFrictionless.ts theme={null}
import { useEffect } from "react";

export function useFrictionless(companyId: string) {
  useEffect(() => {
    // Queued until the script arrives; a second init() is ignored
    const f = ((window as any).Frictionless ||= { q: [] });
    f.init ||= (...args: unknown[]) => f.q.push(["init", args]);
    f.init({ company_id: companyId });

    if (document.getElementById("frictionless-chat")) return;
    const script = document.createElement("script");
    script.id = "frictionless-chat";
    script.src = "https://usefrictionless.com/widget/v1.js";
    script.async = true;
    document.body.appendChild(script);
  }, [companyId]);
}
```

## TypeScript

Types for `window.Frictionless` are at [usefrictionless.com/widget/v1.d.ts](https://usefrictionless.com/widget/v1.d.ts). Save the file into your project, for example as `types/frictionless.d.ts`.

## Loading it async

With `async` or `defer`, the script may arrive after your own code runs. The chat queues calls until it does, so `init()`, `identify()` and `track()` are safe to call at any time:

```html theme={null}
<script>
  window.Frictionless = window.Frictionless || { q: [] };
  ["init", "identify", "track", "setContext", "open", "reset"].forEach(function (m) {
    Frictionless[m] = Frictionless[m] || function () { Frictionless.q.push([m, arguments]); };
  });
  Frictionless.init({ company_id: "YOUR_WORKSPACE_ID" });
</script>
<script async src="https://usefrictionless.com/widget/v1.js"></script>
```

## Single-page apps

Load the chat once, in a layout that stays mounted. It keeps its conversation and open state as visitors move between pages, with or without a full page load. To tell agents where the visitor is, call `track()` when the route changes, for example `track("Viewed pricing")`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.