From 25dae4346a01decc728a25db3d56f3701169a6c4 Mon Sep 17 00:00:00 2001 From: Alexey Novikov Date: Sat, 18 Oct 2025 21:15:04 +1300 Subject: [PATCH 1/3] Fix infinite re-render loop causing performance issues Wrap all callback functions passed to useChatKit in useCallback hooks to prevent infinite re-render cycles. Previously, new callback instances were created on every render, causing useChatKit to reinitialize repeatedly, leading to browser freezes and extreme memory usage (20GB+). Changes: - Extract onClientTool, onResponseEnd, onResponseStart, onThreadChange, and onError into separate useCallback hooks with proper dependencies - This ensures stable function references across renders - Prevents unnecessary re-initialization of the ChatKit component --- components/ChatKitPanel.tsx | 75 ++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/components/ChatKitPanel.tsx b/components/ChatKitPanel.tsx index 067cae2cf..2098c40eb 100644 --- a/components/ChatKitPanel.tsx +++ b/components/ChatKitPanel.tsx @@ -261,27 +261,8 @@ export function ChatKitPanel({ [isWorkflowConfigured, setErrorState] ); - const chatkit = useChatKit({ - api: { getClientSecret }, - theme: { - colorScheme: theme, - ...getThemeConfig(theme), - }, - startScreen: { - greeting: GREETING, - prompts: STARTER_PROMPTS, - }, - composer: { - placeholder: PLACEHOLDER_INPUT, - attachments: { - // Enable attachments - enabled: true, - }, - }, - threadItemActions: { - feedback: false, - }, - onClientTool: async (invocation: { + const handleClientTool = useCallback( + async (invocation: { name: string; params: Record; }) => { @@ -314,20 +295,52 @@ export function ChatKitPanel({ return { success: false }; }, - onResponseEnd: () => { - onResponseEnd(); + [onThemeRequest, onWidgetAction] + ); + + const handleResponseEnd = useCallback(() => { + onResponseEnd(); + }, [onResponseEnd]); + + const handleResponseStart = useCallback(() => { + setErrorState({ integration: null, retryable: false }); + }, [setErrorState]); + + const handleThreadChange = useCallback(() => { + processedFacts.current.clear(); + }, []); + + const handleError = useCallback(({ error }: { error: unknown }) => { + // Note that Chatkit UI handles errors for your users. + // Thus, your app code doesn't need to display errors on UI. + console.error("ChatKit error", error); + }, []); + + const chatkit = useChatKit({ + api: { getClientSecret }, + theme: { + colorScheme: theme, + ...getThemeConfig(theme), }, - onResponseStart: () => { - setErrorState({ integration: null, retryable: false }); + startScreen: { + greeting: GREETING, + prompts: STARTER_PROMPTS, }, - onThreadChange: () => { - processedFacts.current.clear(); + composer: { + placeholder: PLACEHOLDER_INPUT, + attachments: { + // Enable attachments + enabled: true, + }, }, - onError: ({ error }: { error: unknown }) => { - // Note that Chatkit UI handles errors for your users. - // Thus, your app code doesn't need to display errors on UI. - console.error("ChatKit error", error); + threadItemActions: { + feedback: false, }, + onClientTool: handleClientTool, + onResponseEnd: handleResponseEnd, + onResponseStart: handleResponseStart, + onThreadChange: handleThreadChange, + onError: handleError, }); const activeError = errors.session ?? errors.integration; From f987378c87dc3def3469f40a539b76b54c2ca844 Mon Sep 17 00:00:00 2001 From: Alexey Novikov Date: Sat, 18 Oct 2025 21:54:28 +1300 Subject: [PATCH 2/3] Optimize useChatKit configuration with memoization Memoize all configuration objects passed to useChatKit to prevent unnecessary re-initializations of the ChatKit SDK. This reduces performance overhead from the SDK's postMessage architecture. Changes: - Add useMemo import from React - Memoize themeConfig with [theme] dependency - Memoize apiConfig with [getClientSecret] dependency - Memoize startScreenConfig, composerConfig, threadItemActionsConfig with empty dependencies (static configs) - Pass onResponseEnd prop directly instead of wrapping in useCallback - Remove unnecessary render state console.debug logging - Remove comment about ChatKit error handling (self-evident) This ensures the SDK only reinitializes when actual configuration values change, not on every render due to new object references. --- components/ChatKitPanel.tsx | 63 ++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/components/ChatKitPanel.tsx b/components/ChatKitPanel.tsx index 2098c40eb..53c33c258 100644 --- a/components/ChatKitPanel.tsx +++ b/components/ChatKitPanel.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ChatKit, useChatKit } from "@openai/chatkit-react"; import { STARTER_PROMPTS, @@ -298,10 +298,6 @@ export function ChatKitPanel({ [onThemeRequest, onWidgetAction] ); - const handleResponseEnd = useCallback(() => { - onResponseEnd(); - }, [onResponseEnd]); - const handleResponseStart = useCallback(() => { setErrorState({ integration: null, retryable: false }); }, [setErrorState]); @@ -311,33 +307,52 @@ export function ChatKitPanel({ }, []); const handleError = useCallback(({ error }: { error: unknown }) => { - // Note that Chatkit UI handles errors for your users. - // Thus, your app code doesn't need to display errors on UI. console.error("ChatKit error", error); }, []); - const chatkit = useChatKit({ - api: { getClientSecret }, - theme: { + const themeConfig = useMemo( + () => ({ colorScheme: theme, ...getThemeConfig(theme), - }, - startScreen: { + }), + [theme] + ); + + const apiConfig = useMemo(() => ({ getClientSecret }), [getClientSecret]); + + const startScreenConfig = useMemo( + () => ({ greeting: GREETING, prompts: STARTER_PROMPTS, - }, - composer: { + }), + [] + ); + + const composerConfig = useMemo( + () => ({ placeholder: PLACEHOLDER_INPUT, attachments: { - // Enable attachments enabled: true, }, - }, - threadItemActions: { + }), + [] + ); + + const threadItemActionsConfig = useMemo( + () => ({ feedback: false, - }, + }), + [] + ); + + const chatkit = useChatKit({ + api: apiConfig, + theme: themeConfig, + startScreen: startScreenConfig, + composer: composerConfig, + threadItemActions: threadItemActionsConfig, onClientTool: handleClientTool, - onResponseEnd: handleResponseEnd, + onResponseEnd: onResponseEnd, onResponseStart: handleResponseStart, onThreadChange: handleThreadChange, onError: handleError, @@ -346,16 +361,6 @@ export function ChatKitPanel({ const activeError = errors.session ?? errors.integration; const blockingError = errors.script ?? activeError; - if (isDev) { - console.debug("[ChatKitPanel] render state", { - isInitializingSession, - hasControl: Boolean(chatkit.control), - scriptStatus, - hasError: Boolean(blockingError), - workflowId: WORKFLOW_ID, - }); - } - return (
Date: Sat, 18 Oct 2025 22:10:21 +1300 Subject: [PATCH 3/3] Add troubleshooting section for performance issues Document that ChatKit's iframe-based architecture can be affected by browser extensions that interfere with cross-frame messaging (postMessage). Provides guidance for: - Testing in incognito mode to rule out extension interference - Monitoring memory usage for potential issues - Considering server-side integration for production applications This helps users understand architectural tradeoffs and avoid misdiagnosing extension interference as application bugs. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index faecf5343..f3cf69805 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,19 @@ Before deploying your app, you need to verify the domain by adding it to the [Do - Adjust starter prompts, greeting text, [chatkit theme](https://chatkit.studio/playground), and placeholder copy in [`lib/config.ts`](lib/config.ts). - Update the event handlers inside [`components/.tsx`](components/ChatKitPanel.tsx) to integrate with your product analytics or storage. +## Troubleshooting + +### Performance Issues + +ChatKit uses an iframe-based architecture to maintain security isolation for API keys and credentials. While this ensures your sensitive data stays protected, it relies on cross-frame messaging (postMessage) which can be affected by browser extensions. + +**If you experience slow performance or input lag:** + +- **Browser Extensions**: Some extensions (content blockers, privacy tools, developer tools) can interfere with cross-frame communication. Try testing in an incognito window with extensions disabled to rule out interference. +- **Memory Usage**: Monitor browser memory in Task Manager (Shift+Esc in Chrome). Abnormal memory growth may indicate a configuration issue - please [report it](https://github.com/openai/openai-chatkit-starter-app/issues). + +**For production applications**: Consider implementing a server-side integration with OpenAI's API instead of the embedded ChatKit widget. This avoids the iframe architecture entirely and provides better performance and control. See [Advanced Self-Hosting Examples](https://github.com/openai/openai-chatkit-advanced-samples) for guidance. + ## References - [ChatKit JavaScript Library](http://openai.github.io/chatkit-js/)