widget/Framework Integration
Framework Integration
The Intufind widgets work with any JavaScript framework. This guide covers integration patterns for popular frameworks.
React
Basic Integration
Load the widget in a component:
import { useEffect } from 'react';
export function IntufindChat() {
useEffect(() => {
// Load the widget script
const script = document.createElement('script');
script.src = 'https://cdn.intufind.com/chat-loader.js';
script.dataset.publishableKey = 'if_pk_xxx';
script.async = true;
document.body.appendChild(script);
return () => {
// Cleanup on unmount
window.IntufindChatbot?.destroy();
document.body.removeChild(script);
};
}, []);
return null;
}
Custom React Hook
Create a reusable hook:
// hooks/useIntufindChat.ts
import { useEffect, useCallback } from 'react';
interface UseIntufindChatOptions {
publishableKey: string;
title?: string;
greeting?: string;
theme?: Record<string, any>;
}
export function useIntufindChat(options: UseIntufindChatOptions) {
useEffect(() => {
// Set config before loading script
window.intufindConfig = {
publishableKey: options.publishableKey,
title: options.title,
greeting: options.greeting,
theme: options.theme,
};
const script = document.createElement('script');
script.src = 'https://cdn.intufind.com/chat-loader.js';
script.async = true;
document.body.appendChild(script);
return () => {
window.IntufindChatbot?.destroy();
document.body.removeChild(script);
};
}, [options.publishableKey]);
const open = useCallback(() => {
window.IntufindChatbot?.open();
}, []);
const close = useCallback(() => {
window.IntufindChatbot?.close();
}, []);
const toggle = useCallback(() => {
window.IntufindChatbot?.toggle();
}, []);
return { open, close, toggle };
}
Usage:
function App() {
const { open } = useIntufindChat({
publishableKey: 'if_pk_xxx',
title: 'Help Center',
});
return (
<button onClick={open}>
Need help?
</button>
);
}
TypeScript Declarations
Add type declarations for the global API:
// types/intufind.d.ts
interface IntufindChatbotAPI {
open(): void;
close(): void;
toggle(): void;
isOpen(): boolean;
getConfig(): Record<string, any>;
configure(config: Record<string, any>): void;
setTheme(theme: Record<string, any>): void;
setCustomCSS(css: string): void;
destroy(): void;
grantAnalyticsConsent(): void;
revokeAnalyticsConsent(): void;
getAnalyticsConsentStatus(): 'granted' | 'denied' | 'pending';
}
interface IntufindSearchAPI {
open(): void;
close(): void;
toggle(): void;
isOpen(): boolean;
search(query: string): void;
getConfig(): Record<string, any>;
configure(config: Record<string, any>): void;
setTheme(theme: Record<string, any>): void;
setCustomCSS(css: string): void;
destroy(): void;
isInitialized(): boolean;
version: string;
}
declare global {
interface Window {
intufindConfig?: Record<string, any>;
intufindSearchConfig?: Record<string, any>;
IntufindChatbot?: IntufindChatbotAPI;
IntufindSearch?: IntufindSearchAPI;
}
}
export {};
Next.js
App Router (Next.js 13+)
Create a client component:
// components/IntufindWidgets.tsx
'use client';
import { useEffect } from 'react';
interface Props {
publishableKey: string;
chatEnabled?: boolean;
searchEnabled?: boolean;
}
export function IntufindWidgets({
publishableKey,
chatEnabled = true,
searchEnabled = true
}: Props) {
useEffect(() => {
const scripts: HTMLScriptElement[] = [];
if (chatEnabled) {
window.intufindConfig = { publishableKey };
const chatScript = document.createElement('script');
chatScript.src = 'https://cdn.intufind.com/chat-loader.js';
chatScript.async = true;
document.body.appendChild(chatScript);
scripts.push(chatScript);
}
if (searchEnabled) {
window.intufindSearchConfig = { publishableKey };
const searchScript = document.createElement('script');
searchScript.src = 'https://cdn.intufind.com/search-loader.js';
searchScript.async = true;
document.body.appendChild(searchScript);
scripts.push(searchScript);
}
return () => {
window.IntufindChatbot?.destroy();
window.IntufindSearch?.destroy();
scripts.forEach(s => s.remove());
};
}, [publishableKey, chatEnabled, searchEnabled]);
return null;
}
Add to your layout:
// app/layout.tsx
import { IntufindWidgets } from '@/components/IntufindWidgets';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
<IntufindWidgets publishableKey="if_pk_xxx" />
</body>
</html>
);
}
Pages Router
Use next/script:
// pages/_app.tsx
import Script from 'next/script';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Script id="intufind-config" strategy="beforeInteractive">
{`window.intufindConfig = { publishableKey: 'if_pk_xxx' };`}
</Script>
<Script
src="https://cdn.intufind.com/chat-loader.js"
strategy="afterInteractive"
/>
<Component {...pageProps} />
</>
);
}
Environment Variables
Use environment variables for the publishable key:
# .env.local
NEXT_PUBLIC_INTUFIND_KEY=if_pk_xxx
<IntufindWidgets publishableKey={process.env.NEXT_PUBLIC_INTUFIND_KEY!} />
Dynamic Import (Code Splitting)
Load the widget component dynamically:
import dynamic from 'next/dynamic';
const IntufindWidgets = dynamic(
() => import('@/components/IntufindWidgets').then(mod => mod.IntufindWidgets),
{ ssr: false }
);
export default function Page() {
return (
<>
<main>Your content</main>
<IntufindWidgets publishableKey="if_pk_xxx" />
</>
);
}
Vue.js
Vue 3 Composition API
<!-- components/IntufindChat.vue -->
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
interface Props {
publishableKey: string;
title?: string;
}
const props = defineProps<Props>();
onMounted(() => {
window.intufindConfig = {
publishableKey: props.publishableKey,
title: props.title,
};
const script = document.createElement('script');
script.src = 'https://cdn.intufind.com/chat-loader.js';
script.async = true;
document.body.appendChild(script);
});
onUnmounted(() => {
window.IntufindChatbot?.destroy();
});
</script>
<template>
<!-- Renders nothing, widget is injected into body -->
</template>
Vue 3 Composable
// composables/useIntufind.ts
import { onMounted, onUnmounted } from 'vue';
export function useIntufindChat(config: {
publishableKey: string;
title?: string;
greeting?: string;
}) {
onMounted(() => {
window.intufindConfig = config;
const script = document.createElement('script');
script.src = 'https://cdn.intufind.com/chat-loader.js';
script.async = true;
document.body.appendChild(script);
});
onUnmounted(() => {
window.IntufindChatbot?.destroy();
});
return {
open: () => window.IntufindChatbot?.open(),
close: () => window.IntufindChatbot?.close(),
toggle: () => window.IntufindChatbot?.toggle(),
};
}
Nuxt 3
Create a plugin:
// plugins/intufind.client.ts
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig();
window.intufindConfig = {
publishableKey: config.public.intufindKey,
};
useHead({
script: [
{
src: 'https://cdn.intufind.com/chat-loader.js',
async: true,
},
],
});
});
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public: {
intufindKey: process.env.INTUFIND_KEY,
},
},
});
Angular
Service
// services/intufind.service.ts
import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Injectable({ providedIn: 'root' })
export class IntufindService {
private loaded = false;
constructor(@Inject(PLATFORM_ID) private platformId: Object) {}
init(config: { publishableKey: string; title?: string }) {
if (!isPlatformBrowser(this.platformId) || this.loaded) return;
(window as any).intufindConfig = config;
const script = document.createElement('script');
script.src = 'https://cdn.intufind.com/chat-loader.js';
script.async = true;
document.body.appendChild(script);
this.loaded = true;
}
open() {
(window as any).IntufindChatbot?.open();
}
close() {
(window as any).IntufindChatbot?.close();
}
destroy() {
(window as any).IntufindChatbot?.destroy();
this.loaded = false;
}
}
Component
// app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { IntufindService } from './services/intufind.service';
import { environment } from '../environments/environment';
@Component({
selector: 'app-root',
template: `<router-outlet></router-outlet>`,
})
export class AppComponent implements OnInit, OnDestroy {
constructor(private intufind: IntufindService) {}
ngOnInit() {
this.intufind.init({
publishableKey: environment.intufindKey,
});
}
ngOnDestroy() {
this.intufind.destroy();
}
}
Svelte
Component
<!-- IntufindChat.svelte -->
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
export let publishableKey: string;
export let title: string | undefined = undefined;
onMount(() => {
window.intufindConfig = {
publishableKey,
title,
};
const script = document.createElement('script');
script.src = 'https://cdn.intufind.com/chat-loader.js';
script.async = true;
document.body.appendChild(script);
return () => {
window.IntufindChatbot?.destroy();
script.remove();
};
});
</script>
SvelteKit
<!-- +layout.svelte -->
<script lang="ts">
import { browser } from '$app/environment';
import { onMount } from 'svelte';
import { PUBLIC_INTUFIND_KEY } from '$env/static/public';
onMount(() => {
if (!browser) return;
window.intufindConfig = {
publishableKey: PUBLIC_INTUFIND_KEY,
};
const script = document.createElement('script');
script.src = 'https://cdn.intufind.com/chat-loader.js';
script.async = true;
document.body.appendChild(script);
});
</script>
<slot />
Astro
Component
---
// components/IntufindChat.astro
interface Props {
publishableKey: string;
}
const { publishableKey } = Astro.props;
---
<script define:vars={{ publishableKey }}>
window.intufindConfig = {
publishableKey,
};
</script>
<script src="https://cdn.intufind.com/chat-loader.js" async></script>
Usage in layout:
---
// layouts/Layout.astro
import IntufindChat from '../components/IntufindChat.astro';
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My Site</title>
</head>
<body>
<slot />
<IntufindChat publishableKey="if_pk_xxx" />
</body>
</html>
Common Patterns
Conditional Loading
Only load on certain pages:
// React example
function ProductPage() {
const [showChat, setShowChat] = useState(false);
useEffect(() => {
// Load chat only on product pages
setShowChat(true);
}, []);
return (
<>
<ProductContent />
{showChat && <IntufindChat publishableKey="if_pk_xxx" />}
</>
);
}
User Authentication
Pass user JWT for authenticated features:
function App() {
const { user, getToken } = useAuth();
useEffect(() => {
if (user) {
const token = await getToken();
window.IntufindChatbot?.configure({
userJwt: token,
});
}
}, [user]);
return <IntufindChat publishableKey="if_pk_xxx" />;
}
Custom Trigger Buttons
function Header() {
return (
<nav>
<button onClick={() => window.IntufindSearch?.open()}>
<SearchIcon /> Search (⌘K)
</button>
<button onClick={() => window.IntufindChatbot?.open()}>
<ChatIcon /> Help
</button>
</nav>
);
}
Troubleshooting
Widget not loading in SPA
Ensure you're only loading the script once:
const loadedRef = useRef(false);
useEffect(() => {
if (loadedRef.current) return;
loadedRef.current = true;
// Load script...
}, []);
Hydration mismatch
Use client-only rendering:
// Next.js
const IntufindChat = dynamic(() => import('./IntufindChat'), {
ssr: false,
});
Widget persists after navigation
Call destroy() on unmount:
useEffect(() => {
// Load widget...
return () => {
window.IntufindChatbot?.destroy();
};
}, []);
Next Steps
- Chat Widget — Full configuration reference
- Search Widget — Search widget options
- Widget Styling — Theming and customization