widget/Next.js Integration

Next.js Integration

This guide covers the recommended ways to integrate the Intufind chat widget into a Next.js application.

Quick Start (Script Tag)

The simplest approach—add a single script tag to your layout:

Option 1: Data Attributes (Recommended)

Add to your root layout (app/layout.tsx):

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        
        {/* Intufind Chat Widget */}
        <script
          src="https://cdn.intufind.com/widget/v2/loader.js"
          data-publishable-key="if_pk_your_key_here"
          async
        />
      </body>
    </html>
  );
}

That's it! The widget will appear on all pages.

Option 2: Config Object

For more configuration options, use the config object approach:

import Script from 'next/script';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        
        {/* Widget Configuration */}
        <Script id="intufind-config" strategy="beforeInteractive">
          {`
            window.intufindConfig = {
              publishableKey: '${process.env.NEXT_PUBLIC_INTUFIND_PUBLISHABLE_KEY}',
              title: 'AI Assistant',
              greeting: 'How can I help you today?',
              widgetPosition: 'bottom-right',
            };
          `}
        </Script>
        
        {/* Widget Loader */}
        <Script
          src="https://cdn.intufind.com/widget/v2/loader.js"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

React Component Approach

For more control, create a dedicated component:

// components/ChatWidget.tsx
'use client';

import { useEffect } from 'react';

// Extend Window interface for TypeScript
declare global {
  interface Window {
    intufindConfig?: Record<string, unknown>;
    IntufindChatbot?: {
      open: () => void;
      close: () => void;
      toggle: () => void;
      isOpen: () => boolean;
      configure: (config: Record<string, unknown>) => boolean;
      setTheme: (theme: Record<string, unknown>) => boolean;
    };
  }
}

interface ChatWidgetProps {
  publishableKey: string;
  title?: string;
  greeting?: string;
  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
}

export function ChatWidget({
  publishableKey,
  title = 'AI Assistant',
  greeting,
  position = 'bottom-right',
}: ChatWidgetProps) {
  useEffect(() => {
    if (window.IntufindChatbot) return;

    window.intufindConfig = {
      publishableKey,
      title,
      greeting,
      widgetPosition: position,
    };

    const script = document.createElement('script');
    script.src = 'https://cdn.intufind.com/widget/v2/loader.js';
    script.async = true;
    document.body.appendChild(script);

    return () => {
      window.IntufindChatbot?.destroy?.();
    };
  }, [publishableKey, title, greeting, position]);

  return null;
}

Use it in your layout:

// app/layout.tsx
import { ChatWidget } from '@/components/ChatWidget';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <ChatWidget
          publishableKey={process.env.NEXT_PUBLIC_INTUFIND_PUBLISHABLE_KEY!}
          title="Support Bot"
          greeting="Hi! How can I help?"
        />
      </body>
    </html>
  );
}

Environment Variables

Add to your .env.local:

NEXT_PUBLIC_INTUFIND_PUBLISHABLE_KEY=if_pk_your_key_here

Get your publishable key from the Intufind Dashboard.

Programmatic Control

Control the widget from anywhere in your app:

'use client';

export function ChatButton() {
  const openChat = () => {
    window.IntufindChatbot?.open();
  };

  return (
    <button onClick={openChat}>
      Chat with us
    </button>
  );
}

Custom Hook

Create a reusable hook:

// hooks/useChatbot.ts
'use client';

export function useChatbot() {
  return {
    open: () => window.IntufindChatbot?.open(),
    close: () => window.IntufindChatbot?.close(),
    toggle: () => window.IntufindChatbot?.toggle(),
    isOpen: () => window.IntufindChatbot?.isOpen() ?? false,
    configure: (config: Record<string, unknown>) => 
      window.IntufindChatbot?.configure(config) ?? false,
    setTheme: (theme: Record<string, unknown>) => 
      window.IntufindChatbot?.setTheme(theme) ?? false,
  };
}

Conditional Loading

Load the widget only on certain pages:

// app/(marketing)/layout.tsx
import { ChatWidget } from '@/components/ChatWidget';

export default function MarketingLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      {/* Widget only on marketing pages */}
      <ChatWidget publishableKey={process.env.NEXT_PUBLIC_INTUFIND_PUBLISHABLE_KEY!} />
    </>
  );
}

Or exclude from specific pages:

'use client';

import { usePathname } from 'next/navigation';
import { ChatWidget } from '@/components/ChatWidget';

export function ConditionalChatWidget() {
  const pathname = usePathname();
  
  // Don't show on checkout or admin pages
  const excludedPaths = ['/checkout', '/admin'];
  const shouldShow = !excludedPaths.some(path => pathname.startsWith(path));

  if (!shouldShow) return null;

  return <ChatWidget publishableKey={process.env.NEXT_PUBLIC_INTUFIND_PUBLISHABLE_KEY!} />;
}

Theme Integration

Match the widget to your site's theme:

<ChatWidget
  publishableKey={process.env.NEXT_PUBLIC_INTUFIND_PUBLISHABLE_KEY!}
  config={{
    theme: {
      colors: {
        primaryColor: '#6366f1', // Indigo
        widgetBackground: '#ffffff',
        userBubbleBackground: '#6366f1',
      },
      typography: {
        fontFamily: 'var(--font-inter)', // Use your CSS variable
      },
    },
  }}
/>

Dark Mode Support

'use client';

import { useTheme } from 'next-themes';
import { useEffect } from 'react';

export function ChatWidgetWithTheme() {
  const { theme } = useTheme();

  useEffect(() => {
    if (!window.IntufindChatbot) return;

    const isDark = theme === 'dark';
    
    window.IntufindChatbot.setTheme({
      colors: {
        widgetBackground: isDark ? '#1a1a1d' : '#ffffff',
        assistantBubbleBackground: isDark ? '#252528' : '#f3f4f6',
        inputTextColor: isDark ? '#fafafa' : '#1f2937',
      },
    });
  }, [theme]);

  return null;
}

TypeScript Types

For full TypeScript support, add these types:

// types/intufind.d.ts
declare global {
  interface Window {
    intufindConfig?: IntufindConfig;
    IntufindChatbot?: IntufindChatbotAPI;
  }
}

interface IntufindConfig {
  publishableKey: string;
  apiUrl?: string;
  widgetUrl?: string;
  widgetPosition?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
  title?: string;
  subtitle?: string;
  greeting?: string;
  avatar?: string;
  triggerAvatar?: string;
  triggerShowBackground?: boolean;
  triggerIconShowBorder?: boolean;
  showBranding?: boolean;
  debugMode?: boolean;
  theme?: Record<string, unknown>;
  prompts?: Array<{ text: string; icon?: string }>;
}

interface IntufindChatbotAPI {
  open: () => void;
  close: () => void;
  toggle: () => void;
  isOpen: () => boolean;
  getState: () => { isOpen: boolean; config: IntufindConfig };
  getConfig: () => IntufindConfig;
  configure: (config: Partial<IntufindConfig>) => boolean;
  setTheme: (theme: Record<string, unknown>) => boolean;
  setCustomCSS: (css: string) => boolean;
  version: string;
  isInitialized: () => boolean;
  destroy: () => void;
}

export {};

Troubleshooting

Widget not appearing

  1. Check the console for errors
  2. Verify your publishable key starts with if_pk_
  3. Ensure the script loads after hydration (use strategy="afterInteractive")

Hydration mismatch errors

The widget runs client-side only. Use the 'use client' directive and useEffect:

'use client';

import { useEffect, useState } from 'react';

export function ChatWidget() {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  if (!mounted) return null;

  // ... rest of component
}

Multiple widget instances

Ensure you only render the widget component once, typically in your root layout.

Next Steps