Next.js Integration
Integrate BotHarbor chat widget into your Next.js applications with support for both App Router and Pages Router.
App Router Integration
The recommended approach for Next.js 13+ applications using the App Router.
Client Component Approach
'use client'
import { useEffect, useState } from 'react'
interface BotHarborConfig {
botId: string
theme?: 'light' | 'dark' | 'auto'
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
primaryColor?: string
greeting?: string
}
interface BotHarborWidgetProps {
config: BotHarborConfig
}
export default function BotHarborWidget({ config }: BotHarborWidgetProps) {
const [isReady, setIsReady] = useState(false)
const [isOpen, setIsOpen] = useState(false)
useEffect(() => {
const initializeBotHarbor = async () => {
try {
// Set global configuration
window.BOTHARBOR_CONFIG = {
...config,
onReady: () => {
setIsReady(true)
console.log('BotHarbor is ready')
},
onOpen: () => setIsOpen(true),
onClose: () => setIsOpen(false)
}
// Check if script already exists
const existingScript = document.querySelector('script[src="https://botharbor.ai/embed.js"]')
if (existingScript) {
return
}
// Load BotHarbor script
const script = document.createElement('script')
script.src = 'https://botharbor.ai/embed.js'
script.async = true
document.head.appendChild(script)
} catch (error) {
console.error('Failed to initialize BotHarbor:', error)
}
}
initializeBotHarbor()
// Cleanup function
return () => {
const existingScript = document.querySelector('script[src="https://botharbor.ai/embed.js"]')
if (existingScript) {
existingScript.remove()
}
delete window.BOTHARBOR_CONFIG
}
}, [config])
const openChat = () => {
if (window.BotHarbor && isReady) {
window.BotHarbor.open()
}
}
const closeChat = () => {
if (window.BotHarbor) {
window.BotHarbor.close()
}
}
return (
<div className="fixed bottom-4 right-4 z-50">
<button
onClick={openChat}
disabled={!isReady}
className="bg-teal-600 hover:bg-teal-700 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg shadow-lg transition-colors"
>
{isReady ? 'Open Chat' : 'Loading...'}
</button>
</div>
)
}
// Declare global types
declare global {
interface Window {
BOTHARBOR_CONFIG?: any
BotHarbor?: {
open: () => void
close: () => void
sendMessage: (message: string) => void
}
}
}Using in App Router Layout
// app/layout.tsx
import BotHarborWidget from '@/components/BotHarborWidget'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{children}
<BotHarborWidget
config={{
botId: process.env.NEXT_PUBLIC_BOTHARBOR_BOT_ID!,
theme: 'auto',
position: 'bottom-right',
primaryColor: '#14B8A6',
greeting: 'Hello! How can I help you today?'
}}
/>
</body>
</html>
)
}Custom Hook Approach
Create a reusable custom hook for better organization and state management.
useBotHarbor Hook
// hooks/useBotHarbor.ts
'use client'
import { useEffect, useState, useCallback } from 'react'
interface BotHarborConfig {
botId: string
theme?: 'light' | 'dark' | 'auto'
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
primaryColor?: string
greeting?: string
}
interface UseBotHarborReturn {
isReady: boolean
isOpen: boolean
openChat: () => void
closeChat: () => void
sendMessage: (message: string) => void
}
export function useBotHarbor(config: BotHarborConfig): UseBotHarborReturn {
const [isReady, setIsReady] = useState(false)
const [isOpen, setIsOpen] = useState(false)
const initializeBotHarbor = useCallback(async () => {
try {
// Set global configuration
window.BOTHARBOR_CONFIG = {
...config,
onReady: () => {
setIsReady(true)
console.log('BotHarbor is ready')
},
onOpen: () => setIsOpen(true),
onClose: () => setIsOpen(false)
}
// Check if script already exists
const existingScript = document.querySelector('script[src="https://botharbor.ai/embed.js"]')
if (existingScript) {
return
}
// Load BotHarbor script
const script = document.createElement('script')
script.src = 'https://botharbor.ai/embed.js'
script.async = true
document.head.appendChild(script)
} catch (error) {
console.error('Failed to initialize BotHarbor:', error)
}
}, [config])
useEffect(() => {
initializeBotHarbor()
return () => {
const existingScript = document.querySelector('script[src="https://botharbor.ai/embed.js"]')
if (existingScript) {
existingScript.remove()
}
delete window.BOTHARBOR_CONFIG
}
}, [initializeBotHarbor])
const openChat = useCallback(() => {
if (window.BotHarbor && isReady) {
window.BotHarbor.open()
}
}, [isReady])
const closeChat = useCallback(() => {
if (window.BotHarbor) {
window.BotHarbor.close()
}
}, [])
const sendMessage = useCallback((message: string) => {
if (window.BotHarbor && isReady) {
window.BotHarbor.sendMessage(message)
}
}, [isReady])
return { isReady, isOpen, openChat, closeChat, sendMessage }
}Using the Hook
'use client'
import { useBotHarbor } from '@/hooks/useBotHarbor'
export default function ChatWidget() {
const { isReady, isOpen, openChat, closeChat, sendMessage } = useBotHarbor({
botId: process.env.NEXT_PUBLIC_BOTHARBOR_BOT_ID!,
theme: 'auto',
position: 'bottom-right',
primaryColor: '#14B8A6'
})
return (
<div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
<button
onClick={openChat}
disabled={!isReady}
className="bg-teal-600 hover:bg-teal-700 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg shadow-lg transition-colors"
>
{isReady ? 'Open Chat' : 'Loading...'}
</button>
{isOpen && (
<button
onClick={closeChat}
className="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg shadow-lg transition-colors"
>
Close Chat
</button>
)}
<div className="text-xs text-gray-500">
Status: {isOpen ? 'Open' : 'Closed'}
</div>
</div>
)
}Pages Router Integration
For Next.js applications using the traditional Pages Router.
_app.tsx Integration
// pages/_app.tsx
import type { AppProps } from 'next/app'
import { useEffect } from 'react'
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
const initializeBotHarbor = async () => {
try {
// Set global configuration
window.BOTHARBOR_CONFIG = {
botId: process.env.NEXT_PUBLIC_BOTHARBOR_BOT_ID!,
theme: 'auto',
position: 'bottom-right',
primaryColor: '#14B8A6',
greeting: 'Hello! How can I help you?'
}
// Load BotHarbor script
const script = document.createElement('script')
script.src = 'https://botharbor.ai/embed.js'
script.async = true
document.head.appendChild(script)
} catch (error) {
console.error('Failed to initialize BotHarbor:', error)
}
}
initializeBotHarbor()
return () => {
const existingScript = document.querySelector('script[src="https://botharbor.ai/embed.js"]')
if (existingScript) {
existingScript.remove()
}
delete window.BOTHARBOR_CONFIG
}
}, [])
return <Component {...pageProps} />
}
// Declare global types
declare global {
interface Window {
BOTHARBOR_CONFIG?: any
BotHarbor?: {
open: () => void
close: () => void
sendMessage: (message: string) => void
}
}
}Using in Pages
// pages/index.tsx
import { useState, useEffect } from 'react'
export default function HomePage() {
const [chatReady, setChatReady] = useState(false)
useEffect(() => {
// Check if BotHarbor is ready
const checkBotHarbor = () => {
if (window.BotHarbor) {
setChatReady(true)
} else {
setTimeout(checkBotHarbor, 100)
}
}
checkBotHarbor()
}, [])
const openChat = () => {
if (window.BotHarbor && chatReady) {
window.BotHarbor.open()
}
}
return (
<div className="min-h-screen bg-gray-100">
<header className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center py-6">
<h1 className="text-3xl font-bold text-gray-900">
My Next.js App
</h1>
<button
onClick={openChat}
disabled={!chatReady}
className="bg-teal-600 hover:bg-teal-700 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg transition-colors"
>
{chatReady ? 'Need Help?' : 'Loading Chat...'}
</button>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
<div className="border-4 border-dashed border-gray-200 rounded-lg h-96 flex items-center justify-center">
<p className="text-gray-500">Your page content here...</p>
</div>
</div>
</main>
</div>
)
}Environment Configuration
Configure your Next.js application with environment variables for different deployment environments.
Environment Variables
# .env.local
NEXT_PUBLIC_BOTHARBOR_BOT_ID=your-bot-id-here
NEXT_PUBLIC_BOTHARBOR_THEME=auto
NEXT_PUBLIC_BOTHARBOR_POSITION=bottom-right
NEXT_PUBLIC_BOTHARBOR_PRIMARY_COLOR=#14B8A6
# .env.production
NEXT_PUBLIC_BOTHARBOR_BOT_ID=your-production-bot-id
NEXT_PUBLIC_BOTHARBOR_THEME=light
NEXT_PUBLIC_BOTHARBOR_POSITION=bottom-right
NEXT_PUBLIC_BOTHARBOR_PRIMARY_COLOR=#14B8A6Using Environment Variables
// components/BotHarborWidget.tsx
'use client'
import { useBotHarbor } from '@/hooks/useBotHarbor'
export default function BotHarborWidget() {
const { isReady, openChat } = useBotHarbor({
botId: process.env.NEXT_PUBLIC_BOTHARBOR_BOT_ID!,
theme: (process.env.NEXT_PUBLIC_BOTHARBOR_THEME as 'light' | 'dark' | 'auto') || 'auto',
position: (process.env.NEXT_PUBLIC_BOTHARBOR_POSITION as any) || 'bottom-right',
primaryColor: process.env.NEXT_PUBLIC_BOTHARBOR_PRIMARY_COLOR || '#14B8A6'
})
return (
<button
onClick={openChat}
disabled={!isReady}
className="fixed bottom-4 right-4 z-50 bg-teal-600 hover:bg-teal-700 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg shadow-lg transition-colors"
>
{isReady ? 'Chat with us' : 'Loading...'}
</button>
)
}Best Practices
Next.js Specific Tips
- ✓Use 'use client' directive for client-side components
- ✓Leverage NEXT_PUBLIC_ environment variables
- ✓Use custom hooks for reusable logic
- ✓Implement proper cleanup in useEffect
Performance & SEO
- •Load scripts asynchronously to avoid blocking
- •Use dynamic imports for code splitting
- •Implement loading states for better UX
- •Consider SSR implications for chat widgets
