From 3425cf245227678d5d591efe8e7844c58f460724 Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:42:01 +0000 Subject: [PATCH] Harden Crisp chat widget load and report failures to PostHog The Crisp chat widget was injected in src/theme/Root.tsx with no load-failure handling, so when Crisp's backend is slow or unreachable it surfaced a raw "Request timeout" to docs readers that followed them across pages. Guard the script load with an onerror handler and a load timeout so a failed load degrades quietly (removes the dangling script) instead of showing a raw timeout. Also report the failure via PostHog's captureException so we can quantify how often Crisp fails to load, since the docs site currently captures no $exception events. Generated-By: PostHog Code Task-Id: 8b4922b2-08fa-46d0-8d38-31881bfb123b --- src/theme/Root.tsx | 51 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx index fdd2f955..2d8fad41 100644 --- a/src/theme/Root.tsx +++ b/src/theme/Root.tsx @@ -8,11 +8,32 @@ const LEVEL_3_PADDING_CLASS = "menu__link--level-3" const SIDEBAR_MENU_SELECTOR = ".theme-doc-sidebar-menu" const CRISP_WEBSITE_ID = "24b58135-86c7-4e4b-bf6e-cca862bc4b26" const CRISP_SCRIPT_SRC = "https://client.crisp.chat/l.js" +const CRISP_LOAD_TIMEOUT_MS = 15000 declare global { interface Window { $crisp?: unknown[] CRISP_WEBSITE_ID?: string + posthog?: { + captureException?: ( + error: unknown, + properties?: Record, + ) => void + } + } +} + +function reportCrispFailure(reason: string) { + // Surface the failure to PostHog so we can quantify how often the Crisp + // widget fails to load, instead of only seeing a raw "Request timeout" in + // the widget. posthog is injected globally by posthog-docusaurus. + try { + window.posthog?.captureException?.( + new Error(`Crisp chat widget failed to load: ${reason}`), + { source: "crisp-widget", crisp_script_src: CRISP_SCRIPT_SRC }, + ) + } catch { + // Never let error reporting itself break page rendering. } } @@ -47,6 +68,36 @@ export default function Root({ children }: Props): ReactElement { const script = document.createElement("script") script.src = CRISP_SCRIPT_SRC script.async = true + + // Guard the load: if Crisp's backend is slow or unreachable the widget + // otherwise surfaces a raw "Request timeout" to the reader. Degrade + // quietly and report the failure instead. + let settled = false + + const timeoutId = window.setTimeout(() => { + if (settled) { + return + } + settled = true + script.remove() + reportCrispFailure("timeout") + }, CRISP_LOAD_TIMEOUT_MS) + + script.addEventListener("load", () => { + settled = true + window.clearTimeout(timeoutId) + }) + + script.addEventListener("error", () => { + if (settled) { + return + } + settled = true + window.clearTimeout(timeoutId) + script.remove() + reportCrispFailure("script error") + }) + document.head.appendChild(script) }, [])