From 45f755bf30a244279778d1f88184375683be000e Mon Sep 17 00:00:00 2001 From: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com> Date: Mon, 31 Aug 2026 05:14:03 +0000 Subject: [PATCH 1/8] fix(js-core): prefetch the surveys bundle instead of preloading it (ENG-2350) (#9009) --- packages/js-core/src/lib/common/setup.ts | 6 ++--- .../src/lib/common/tests/setup.test.ts | 2 +- .../src/lib/survey/tests/widget.test.ts | 10 ++++----- packages/js-core/src/lib/survey/widget.ts | 22 +++++++++++++------ 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/js-core/src/lib/common/setup.ts b/packages/js-core/src/lib/common/setup.ts index d9b2d405edda..97abdfdf2fd7 100644 --- a/packages/js-core/src/lib/common/setup.ts +++ b/packages/js-core/src/lib/common/setup.ts @@ -4,7 +4,7 @@ import { addCleanupEventListeners, addEventListeners } from "@/lib/common/event- import { Logger } from "@/lib/common/logger"; import { getIsSetup, setIsSetup } from "@/lib/common/status"; import { filterSurveys, getIsDebug, isNowExpired, wrapThrows } from "@/lib/common/utils"; -import { addLiveRegionContainer, closeSurvey, preloadSurveysScript } from "@/lib/survey/widget"; +import { addLiveRegionContainer, closeSurvey, prefetchSurveysScript } from "@/lib/survey/widget"; import { DEFAULT_USER_STATE_NO_USER_ID } from "@/lib/user/state"; import { sendUpdatesToBackend } from "@/lib/user/update"; import { fetchWorkspaceState } from "@/lib/workspace/state"; @@ -344,8 +344,8 @@ export const setup = async ( // first survey announces its opening into it. addLiveRegionContainer(); - // Preload surveys script so it's ready when a survey triggers - preloadSurveysScript(configInput.appUrl); + // Prefetch surveys script so it's warm in the cache when a survey triggers + prefetchSurveysScript(configInput.appUrl); setIsSetup(true); logger.debug("Set up complete"); diff --git a/packages/js-core/src/lib/common/tests/setup.test.ts b/packages/js-core/src/lib/common/tests/setup.test.ts index b90163bc4fe9..dcda5641fb9b 100644 --- a/packages/js-core/src/lib/common/tests/setup.test.ts +++ b/packages/js-core/src/lib/common/tests/setup.test.ts @@ -73,7 +73,7 @@ vi.mock("@/lib/survey/no-code-action", () => ({ // 9) Mock survey widget vi.mock("@/lib/survey/widget", () => ({ closeSurvey: vi.fn(), - preloadSurveysScript: vi.fn(), + prefetchSurveysScript: vi.fn(), addLiveRegionContainer: vi.fn(), })); diff --git a/packages/js-core/src/lib/survey/tests/widget.test.ts b/packages/js-core/src/lib/survey/tests/widget.test.ts index f2140282c453..6dd89d7eec85 100644 --- a/packages/js-core/src/lib/survey/tests/widget.test.ts +++ b/packages/js-core/src/lib/survey/tests/widget.test.ts @@ -694,22 +694,22 @@ describe("widget-file", () => { }); }); - test("preloadSurveysScript adds a preload link and deduplicates subsequent calls", () => { + test("prefetchSurveysScript adds a prefetch link and deduplicates subsequent calls", () => { const createElementSpy = vi.spyOn(document, "createElement"); const appendChildSpy = vi.spyOn(document.head, "appendChild"); - widget.preloadSurveysScript("https://fake.app"); + widget.prefetchSurveysScript("https://fake.app"); expect(createElementSpy).toHaveBeenCalledWith("link"); expect(appendChildSpy).toHaveBeenCalledTimes(1); const linkEl = createElementSpy.mock.results[0].value as Record; - expect(linkEl.rel).toBe("preload"); - expect(linkEl.as).toBe("script"); + expect(linkEl.rel).toBe("prefetch"); + expect(linkEl.as).toBeUndefined(); expect(linkEl.href).toBe("https://fake.app/js/surveys.umd.cjs"); // Second call should be a no-op (deduplication) - widget.preloadSurveysScript("https://fake.app"); + widget.prefetchSurveysScript("https://fake.app"); expect(appendChildSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/js-core/src/lib/survey/widget.ts b/packages/js-core/src/lib/survey/widget.ts index 2549bcfe7750..420a5bf6d29a 100644 --- a/packages/js-core/src/lib/survey/widget.ts +++ b/packages/js-core/src/lib/survey/widget.ts @@ -369,17 +369,25 @@ const loadFormbricksSurveysExternally = (): Promise => { return surveysLoadPromise; }; -let isPreloaded = false; +let isPrefetched = false; -export const preloadSurveysScript = (appUrl: string): void => { - // Don't preload if already loaded or already preloading +/** + * Warms the browser cache with the surveys bundle so a triggered survey renders without a cold fetch. + * + * `prefetch`, not `preload`: preload claims the page needs the file now, so Chrome fetches it at high + * priority and warns when it goes unused. Most page views never trigger a survey, so we were outbidding + * the host page's own critical resources for a ~260 KB bundle we usually never run. The later