Why the Salespeak widget won't slow down your site
The embed below is one deferred script tag. Everything expensive happens after your page is interactive, inside an isolated iframe — so it cannot regress your Core Web Vitals.
<script
src="https://app.salespeak.ai/widget.js"
data-org-id="xxx-xxx-xxx-xxx"
data-launcher="dynamic-launcher"
defer></script>
The headline: the widget mounts after the browser
fires the
load event on your page. By the time any Salespeak iframe
attaches to the DOM, your page is
already interactive, content is painted, and your Time to Interactive
(TTI) is locked in. The widget
cannot push it.
What “fast” means here
Google PageSpeed Insights judges your page mostly on three things:
- FCPFirst Contentful Paint — when the user sees something.
- LCPLargest Contentful Paint — when the main content is visible.
- TTITime to Interactive — when the page can respond within ~50ms.
load, the entire scoring window has already closed.
1. The script is deferred and non-blocking
The defer attribute tells the browser to download
widget.js in parallel with HTML
parsing and execute it only after the document has finished parsing. Your
HTML, CSS, fonts, hero image, and
above-the-fold JavaScript never wait for it. Removing defer
would still work — but with
it, the script is provably outside the critical rendering path.
2. The widget waits for your page before mounting
Once the script does run, it does not immediately inject anything heavy.
It registers a
readystatechange listener and waits until
document.readyState === 'complete'
— the same moment that fires the window.onload event. Only then
does it attach the
launcher and the iframe to the DOM. That ordering is intentional: it guarantees
the widget can never
compete with your page for main-thread time during the period that PageSpeed
and Core Web Vitals are
actually measuring.
3. The bundle is small, minified, and code-split per launcher
The bootstrap script does almost nothing on its own. It reads the
data-* attributes, fetches
your widget settings, and decides which launcher to render. Every launcher
(dynamic-launcher, chat-icon,
sticky-input, sidebar,
box-launcher, inline-cards,
persona-switch, dynamic-bar,
and the rest) is loaded as a separate webpack chunk — so the visitor only
downloads the chunk for
the launcher you configured, not the union of every launcher we ship. Optional
features follow the same
pattern: the URLPattern polyfill is fetched only when the browser
lacks it, and exit-intent
detection is loaded lazily after render and is a complete no-op for orgs
that haven't enabled it.
4. The chat UI lives in an isolated iframe
The expensive code — the chat React app, the model client, fonts, themes,
message rendering —
does not run on your page. It runs in an iframe pointing at
brain.salespeak.ai. That iframe
has its own browsing context, JS thread, CSS scope, memory, and event loop.
That gives you three concrete
guarantees:
- No conflictsNo global variable, CSS, or font collisions with your stack.
- No layout thrashSalespeak rendering can't block your main thread or trigger reflows on your DOM.
- No memory creepLong chat sessions stay sandboxed; reclaimed when the iframe is removed.
The host page only exchanges small postMessage events with the
iframe — lightweight,
asynchronous, and never blocking.
5. Settings load from a CDN with high priority
Configuration is fetched from a CloudFront endpoint with the Fetch Priority
API set to high,
so it's scheduled ahead of lower-priority subresources. The application backend
is used only as a fallback
if the CDN response fails. Visitors who don't match a display condition,
are in a restricted region, or
are on mobile with launchers disabled fall back silently to a hidden tracking-only
iframe
(display: none) instead of mounting any UI — so for most non-targeted
page views the
widget's runtime cost is essentially the bootstrap bundle plus one CDN-served
settings request.
Net effect for data-launcher="dynamic-launcher":
your page loads a small deferred script, makes one CDN request, and —
only after window
load — mounts a single iframe (or, for users who don't match
any display condition, a
hidden tracking iframe). All of the heavy chat rendering happens inside
the iframe and never competes
with your page's JS, CSS, or layout work. By construction, the widget
cannot regress your FCP, LCP, or
TTI.