React & Next.js - Docs menu

React & Next.js

The Navigic widget works in any React or Next.js app. This guide shows how to load the script, initialize the widget, and handle identity in a React-friendly way.

Basic Setup

Create a component that loads the embed script and initializes the widget:

"use client"; // Next.js App Router
 
import { useEffect } from "react";
 
const EMBED_SCRIPT_URL = "https://releases.navigic.ai/embed/embed.js";
const WORKSPACE_ID = "pk_wk_YOUR_KEY";
 
export function NavigicChat() {
  useEffect(() => {
    // Don't load twice
    if (document.querySelector(`script[src^="${EMBED_SCRIPT_URL}"]`)) {
      return;
    }
 
    const script = document.createElement("script");
    script.src = EMBED_SCRIPT_URL;
    script.async = true;
    script.dataset.apiKey = WORKSPACE_ID;
    script.dataset.manual = "true";
 
    script.onload = () => {
      window.NavigicWidget?.init({
        workspaceId: WORKSPACE_ID,
        displayMode: "bubble",
        position: "bottom-right",
        showToolCalls: true,
        showReasoning: false,
      });
    };
 
    document.body.appendChild(script);
 
    return () => {
      window.NavigicWidget?.destroy();
    };
  }, []);
 
  return null;
}

Then render it in your layout:

// app/layout.tsx (Next.js App Router)
import { NavigicChat } from "@/components/navigic-chat";
 
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <NavigicChat />
      </body>
    </html>
  );
}

TypeScript

Add a type declaration for the NavigicWidget global:

// types/navigic.d.ts
interface NavigicWidgetAPI {
  init(config: {
    workspaceId: string;
    displayMode?: "bubble" | "panel" | "inline";
    position?: "bottom-right" | "bottom-left";
    primaryColor?: string;
    title?: string;
    greeting?: string;
    gatewayUrl?: string;
    panelWidth?: string;
    panelSide?: "left" | "right";
    targetElement?: string | HTMLElement;
    mode?: "live" | "mock";
    showToolCalls?: boolean;
    showReasoning?: boolean;
  }): void;
  destroy(): void;
  open(): void;
  close(): void;
  identify(
    userId: string,
    traits?: Record<string, string>,
    auth?: { userHash?: string },
  ): void;
  reset(): void;
}
 
declare global {
  interface Window {
    NavigicWidget?: NavigicWidgetAPI;
  }
}
 
export {};

Omit showToolCalls and showReasoning when the page should inherit the dashboard embed-key policy. Set them only when this specific install should make an explicit visibility request.

Identity with Auth Providers

If you use an auth provider (Clerk, NextAuth, Auth0, etc.), identify the user after they sign in.

With Clerk

"use client";
 
import { useEffect } from "react";
import { useUser } from "@clerk/nextjs";
 
export function NavigicIdentify() {
  const { user, isSignedIn } = useUser();
 
  useEffect(() => {
    if (!isSignedIn || !user) return;
 
    // Fetch HMAC from your API route
    fetch("/api/embed-identity", { method: "POST" })
      .then((res) => res.json())
      .then(({ userHash }) => {
        window.NavigicWidget?.identify(
          user.id,
          {
            email: user.primaryEmailAddress?.emailAddress ?? "",
            name: user.fullName ?? "",
          },
          { userHash },
        );
      });
  }, [isSignedIn, user]);
 
  return null;
}

With NextAuth

"use client";
 
import { useEffect } from "react";
import { useSession } from "next-auth/react";
 
export function NavigicIdentify() {
  const { data: session } = useSession();
 
  useEffect(() => {
    if (!session?.user) return;
 
    fetch("/api/embed-identity", { method: "POST" })
      .then((res) => res.json())
      .then(({ userHash }) => {
        window.NavigicWidget?.identify(
          session.user.id,
          {
            email: session.user.email ?? "",
            name: session.user.name ?? "",
          },
          { userHash },
        );
      });
  }, [session]);
 
  return null;
}

Reset on Logout

Call reset() in your sign-out handler:

function handleSignOut() {
  window.NavigicWidget?.reset();
  // ... your sign-out logic
}

Inline Mode in React

For inline mode, pass a ref to the container element:

"use client";
 
import { useEffect, useRef } from "react";
 
export function NavigicInline() {
  const containerRef = useRef<HTMLDivElement>(null);
 
  useEffect(() => {
    if (!containerRef.current) return;
 
    const script = document.createElement("script");
    script.src = "https://releases.navigic.ai/embed/embed.js";
    script.async = true;
    script.dataset.apiKey = "pk_wk_YOUR_KEY";
    script.dataset.manual = "true";
 
    script.onload = () => {
      window.NavigicWidget?.init({
        workspaceId: "pk_wk_YOUR_KEY",
        displayMode: "inline",
        targetElement: containerRef.current!,
      });
    };
 
    document.body.appendChild(script);
 
    return () => {
      window.NavigicWidget?.destroy();
    };
  }, []);
 
  return <div ref={containerRef} style={{ height: 600, width: "100%" }} />;
}

Next.js Pages Router

If you're using the Pages Router instead of App Router, the setup is the same — just remove the "use client" directive and use _app.tsx:

// pages/_app.tsx
import type { AppProps } from "next/app";
import { NavigicChat } from "@/components/navigic-chat";
 
export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Component {...pageProps} />
      <NavigicChat />
    </>
  );
}