Patterns for building AI chatbot frontends using OpenAI ChatKit with Next.js App Router, including custom backend integration via ChatKit Python SDK.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
name: openai-chatkit-frontend description: > Patterns for building AI chatbot frontends using OpenAI ChatKit with Next.js App Router, including custom backend integration via ChatKit Python SDK. version: 1.0.0
OpenAI ChatKit Frontend Skill
When to use this Skill
Use this Skill whenever you are:
- Building a chat UI for an AI-powered application.
- Integrating OpenAI ChatKit with a Next.js App Router frontend.
- Connecting ChatKit to a custom FastAPI backend.
- Implementing real-time streaming chat responses.
- Adding a conversational interface to an existing application.
This Skill works for any Next.js application that needs a production-ready chat interface with AI capabilities.
Core goals
- Build polished, production-ready chat UIs with minimal code.
- Integrate seamlessly with custom backends using ChatKit Python SDK.
- Maintain responsive, accessible chat interfaces.
- Follow consistent patterns for ChatKit configuration.
- Enable real-time streaming for natural conversations.
Technology Stack
| Component | Technology | Version |
|---|---|---|
| Frontend Framework | Next.js (App Router) | >=16.0.0 |
| ChatKit React | @openai/chatkit-react | latest |
| ChatKit Core | chatkit.js (CDN) | latest |
| Backend SDK | chatkit (Python) | latest |
| Styling | Tailwind CSS | >=3.0 |
Installation
Frontend (Next.js)
npm install @openai/chatkit-react
Backend (FastAPI)
pip install chatkit openai
Required environment variable:
OPENAI_API_KEY=sk-your-api-key-here
Architecture Overview
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Next.js Frontend β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ChatKit Component β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β β β Message β β Composer β β Thread β β β
β β β List β β Input β β Sidebar β β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β useChatKit Hook β β
β β - getClientSecret() β fetches token from backend β β
β β - control object β manages ChatKit state β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FastAPI Backend β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β POST /api/chatkit/session β β
β β - Creates ChatKit session β β
β β - Returns client_secret for frontend β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ChatKit Python SDK β β
β β - Handles AI responses β β
β β - Processes tool calls β β
β β - Manages conversation state β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Two Integration Approaches
Approach 1: OpenAI-Hosted Backend (Simpler)
Use OpenAI's infrastructure to host your chat backend. Best for:
- Quick prototypes
- Simple chatbots without custom business logic
- Agents built in OpenAI's Agent Builder
// Frontend only - OpenAI handles the backend
import { ChatKit, useChatKit } from '@openai/chatkit-react';
export function Chat() {
const { control } = useChatKit({
api: {
async getClientSecret() {
// Fetch from your API that calls OpenAI
const res = await fetch('/api/chatkit/session', { method: 'POST' });
const { client_secret } = await res.json();
return client_secret;
},
},
});
return <ChatKit control={control} className="h-[600px]" />;
}
Approach 2: Custom Backend (Recommended for Phase III)
Use ChatKit Python SDK with your own FastAPI backend. Best for:
- Custom business logic and tool execution
- Integration with existing databases
- Full control over AI behavior
This is the recommended approach for the hackathon Phase III.
Frontend Implementation
1. Add ChatKit Script
In your app/layout.tsx:
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<Script
src="https://cdn.platform.openai.com/deployments/chatkit/chatkit.js"
strategy="beforeInteractive"
/>
</head>
<body>{children}</body>
</html>
);
}
2. ChatKit Component
'use client';
import { ChatKit, useChatKit } from '@openai/chatkit-react';
import { useSession } from 'next-auth/react'; // or your auth solution
interface ChatWidgetProps {
className?: string;
}
export function ChatWidget({ className = '' }: ChatWidgetProps) {
const { data: session } = useSession();
const { control } = useChatKit({
api: {
async getClientSecret(existingSecret) {
// Handle token refresh if existing secret is provided
if (existingSecret) {
// Optionally refresh the session
}
// Fetch new client secret from your backend
const response = await fetch('/api/chatkit/session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${session?.accessToken}`,
},
body: JSON.stringify({
user_id: session?.user?.id,
}),
});
if (!response.ok) {
throw new Error('Failed to create chat session');
}
const { client_secret } = await response.json();
return client_secret;
},
},
});
return (
<ChatKit
control={control}
className={`rounded-lg shadow-lg ${className}`}
/>
);
}
3. Chat Page
// app/chat/page.tsx
import { ChatWidget } from '@/components/ChatWidget';
export default function ChatPage() {
return (
<div className="container mx-auto py-8">
<h1 className="text-2xl font-bold mb-6">Chat Assistant</h1>
<ChatWidget className="h-[600px] w-full max-w-2xl" />
</div>
);
}
4. Toggle Button Integration
Add chat to existing pages with a toggle button:
'use client';
import { useState } from 'react';
import { ChatWidget } from '@/components/ChatWidget';
import { MessageCircle, X } from 'lucide-react';
export function ChatToggle() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
{/* Floating chat button */}
<button
onClick={() => setIsOpen(!isOpen)}
className="fixed bottom-6 right-6 p-4 bg-blue-600 text-white rounded-full shadow-lg hover:bg-blue-700 transition-colors z-50"
aria-label={isOpen ? 'Close chat' : 'Open chat'}
>
{isOpen ? <X size={24} /> : <MessageCircle size={24} />}
</button>
{/* Chat panel */}
{isOpen && (
<div className="fixed bottom-24 right-6 w-[380px] h-[500px] z-40">
<ChatWidget className="h-full w-full" />
</div>
)}
</>
);
}
Backend Implementation (FastAPI)
Session Endpoint
# app/routers/chatkit.py
from fastapi import APIRouter, Depends, HTTPException
from openai import OpenAI
from pydantic import BaseModel
import os
router = APIRouter(prefix="/api/chatkit", tags=["chatkit"])
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
class SessionRequest(BaseModel):
user_id: str | None = None
class SessionResponse(BaseModel):
client_secret: str
@router.post("/session", response_model=SessionResponse)
async def create_session(
request: SessionRequest,
current_user = Depends(get_current_user),
):
"""Create a ChatKit session and return client secret."""
try:
session = client.chatkit.sessions.create(
# Configure your ChatKit session
# This depends on your ChatKit setup
)
return SessionResponse(client_secret=session.client_secret)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
Custom Backend with ChatKit Python SDK
For full control, implement your own chat handling:
# app/routers/chat_custom.py
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from typing import Optional
router = APIRouter(prefix="/api/{user_id}", tags=["chat"])
class ChatRequest(BaseModel):
message: str
conversation_id: Optional[int] = None
class ChatResponse(BaseModel):
conversation_id: int
response: str
@router.post("/chat", response_model=ChatResponse)
async def chat(
user_id: str,
request: ChatRequest,
current_user = Depends(get_current_user),
):
"""Process chat message through AI agent."""
# Your existing chat implementation
# (See openai-agents-sdk-mcp-backend skill)
pass
Styling ChatKit
Custom Theming
/* styles/chatkit.css */
/* Container styling */
openai-chatkit {
--chatkit-primary-color: #3b82f6;
--chatkit-background: #ffffff;
--chatkit-text-color: #1f2937;
--chatkit-border-radius: 0.5rem;
}
/* Dark mode support */
.dark openai-chatkit {
--chatkit-background: #1f2937;
--chatkit-text-color: #f3f4f6;
}
Responsive Design
export function ChatWidget() {
return (
<ChatKit
control={control}
className="
h-[400px] w-full
sm:h-[500px]
md:h-[600px]
lg:h-[700px]
"
/>
);
}
Error Handling
Frontend Error States
'use client';
import { ChatKit, useChatKit } from '@openai/chatkit-react';
import { useState } from 'react';
export function ChatWidget() {
const [error, setError] = useState<string | null>(null);
const { control } = useChatKit({
api: {
async getClientSecret() {
try {
const res = await fetch('/api/chatkit/session', { method: 'POST' });
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const { client_secret } = await res.json();
setError(null);
return client_secret;
} catch (e) {
setError('Failed to connect to chat service');
throw e;
}
},
},
});
if (error) {
return (
<div className="p-4 bg-red-50 text-red-700 rounded-lg">
<p>{error}</p>
<button
onClick={() => setError(null)}
className="mt-2 text-sm underline"
>
Try again
</button>
</div>
);
}
return <ChatKit control={control} className="h-[600px]" />;
}
Authentication Integration
With Better Auth
'use client';
import { ChatKit, useChatKit } from '@openai/chatkit-react';
import { useAuth } from '@/lib/auth-client';
export function AuthenticatedChat() {
const { session, token } = useAuth();
const { control } = useChatKit({
api: {
async getClientSecret() {
if (!token) {
throw new Error('Not authenticated');
}
const res = await fetch('/api/chatkit/session', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
const { client_secret } = await res.json();
return client_secret;
},
},
});
if (!session) {
return <p>Please log in to use the chat assistant.</p>;
}
return <ChatKit control={control} className="h-[600px]" />;
}
Best Practices
DO:
- Load ChatKit script early - Use
strategy="beforeInteractive"in Next.js. - Handle authentication - Pass JWT tokens to your session endpoint.
- Implement error states - Show user-friendly messages on failures.
- Make it responsive - Adjust chat dimensions for different screens.
- Provide loading states - Show spinners while connecting.
DON'T:
- Expose API keys - Never put OPENAI_API_KEY in frontend code.
- Skip error handling - Always catch and display errors gracefully.
- Hardcode dimensions - Use responsive classes for sizing.
- Ignore accessibility - Ensure chat is keyboard navigable.
- Block the main thread - Use async/await properly.
Testing
Component Testing
import { render, screen, waitFor } from '@testing-library/react';
import { ChatWidget } from '@/components/ChatWidget';
// Mock fetch
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ client_secret: 'test-secret' }),
})
) as jest.Mock;
describe('ChatWidget', () => {
it('renders without crashing', async () => {
render(<ChatWidget />);
await waitFor(() => {
expect(screen.getByRole('region')).toBeInTheDocument();
});
});
it('handles session creation error', async () => {
(fetch as jest.Mock).mockRejectedValueOnce(new Error('Network error'));
render(<ChatWidget />);
await waitFor(() => {
expect(screen.getByText(/failed/i)).toBeInTheDocument();
});
});
});
References
More by mub7865
View allComplete guide for local Kubernetes development with Minikube: installation, configuration, image management, addons, networking, and troubleshooting for efficient local development workflows.
Complete patterns for deploying applications on Kubernetes: Deployments, Services, ConfigMaps, Secrets, health probes, resource management, and production-ready configurations for any application.
Complete patterns for creating and managing Helm charts: chart structure, templates, values, dependencies, and deployment workflows for packaging Kubernetes applications.
Complete patterns for containerizing applications with Docker: Dockerfiles, multi-stage builds, layer optimization, security best practices, and production-ready configurations for Python/FastAPI and Node.js/Next.js apps.
