Performance and CSP

What the widget costs your page, in the order it happens, and the sources to allow when your site sends a Content Security Policy.

Last updated

Sizes and budgets#

BundleLoadsSize (brotli)Budget
LauncherOn every page with the snippet7.8 KB8 KB
HelperAfter your page's load event, only when your page passes a user or an unread reply is waiting2.3 KB3 KB
Chat panelWhen a visitor opens the chat, once per page view. Never before your page's load event59.5 KB60 KB
Language fileOnly for a visitor whose language needs it, one of 8up to 2.9 KB3 KB

The chat panel loads only when a visitor opens the chat. A page that passes a user (with identify or bettercomConfig.user), or a visitor with an unread reply, gets the small helper after your load event instead: it sends the user and draws the preview bubble. A page with neither fetches nothing more.

Sizes are brotli at quality 11, from the widget build of . The widget build measures every bundle the same way and fails when one is over its budget, so a release cannot quietly grow past these lines.

What loads, in order#

  1. Your page parses and renders.

    The script tag is async, so the browser keeps working on your page while it downloads.

  2. The launcher fetches your workspace settings.

    Brand color, corner, size and copy usually arrive before anything is drawn. If they take longer than 3 seconds, the launcher goes ahead with defaults.

  3. It records the page view.

    With tracking on: one small request, then one per in-page navigation.

  4. It waits for your page.

    After the window load event plus Show after (1 second by default), or, with Wait for the page to finish drawing (Settings, Widget, Loading) on, after your Largest Contentful Paint settles (at most 8 seconds).

  5. It draws the launcher.

    Inside a closed shadow root. While the panel is closed and the tab is visible, it checks for unread messages every 30 seconds and when the tab comes back into view. Hidden tabs do not ask.

  6. After load, only if needed: the helper.

    When your page passed a user, or an unread reply is waiting to be shown in the preview bubble, the helper downloads now, after your load event. It sends the user and draws the bubble. The chat panel does not load.

  7. A visitor opens the chat.

    The chat panel downloads on the first open, once per page view, and opens its connection to the chat server.

Compression and caching#

The server keeps a brotli and a gzip copy of each bundle and sends the best one your visitor's browser accepts. In production the loader is cached for an hour (Cache-Control: public, max-age=3600). The helper and the chat panel are the same files for every workspace and are cached for a year under their ?v= version (public, max-age=31536000, immutable); a new build is a new URL.

Content Security Policy#

If your site sends a Content-Security-Policy header, allow the widget's host in these directives. YOUR_HOST is the host in your install snippet.

Directives to add
script-src 'self' https://YOUR_HOST;
connect-src 'self' https://YOUR_HOST wss://YOUR_HOST;
img-src 'self' https://YOUR_HOST blob:;
media-src 'self' https://YOUR_HOST blob:;
DirectiveWhy the widget needs it
script-srcThe loader, the helper, the chat panel and the language files, all served from your host.
connect-src Settings, page views, unread checks and the waveform of a voice message over HTTPS, and the live chat connection over a WebSocket (wss:), which also sets up voice calls.
img-src Your logo, avatars and uploaded images, plus blob: for previews of files a visitor is about to send.
media-srcVoice messages and video files, plus blob: to play a voice message back before it is sent.
style-src Nothing to add: the widget's styles are constructed stylesheets inside its shadow root, which style-src does not govern. Safari before 16.4 has no constructed stylesheets and uses a <style> element there instead, which needs style-src 'unsafe-inline'.

The widget loads no fonts and no third-party scripts, so font-src needs nothing.

Voice calls: the call is set up over the chat connection above. Its audio then travels over WebRTC, through the STUN and TURN servers the chat server names when a call starts. We have not yet tested calls under a strict policy, so this page lists no sources for them.

Check it yourself#

  1. Open your page with developer tools on the Network tab, cache disabled, and filter by widget.
  2. Reload. You should see the loader and the settings request. Before the load event there is nothing else. After it, helper.js appears only when your page passed a user or an unread reply is waiting; conversation.js does not. The size column shows the compressed transfer size.
  3. Open the chat. conversation.js appears now, once.
  4. Turn on Wait for the page to finish drawing in Settings, Widget and reload: the launcher now appears only after your page's largest element has painted.