Shrinking a chat widget 80% by dropping the UI library
The FluentBot chat widget embeds on customers’ marketing pages. Which means its bundle isn’t our problem — it’s their Largest Contentful Paint. A UI library that felt free in our app was very much not free on theirs.
The measurement that started it
A customer flagged a Core Web Vitals regression after embedding us. The widget shipped ~220kB of JS to render what is, structurally, a button and a scroll area.
A dependency’s cost is paid by whoever loads it. On an embedded widget, that’s never you — it’s the host page.
What we cut
I catalogued every UI primitive actually in use — it was a short list. Each got rebuilt as a small vanilla Svelte component, scoped to a shadow root so it couldn’t leak into (or inherit from) the host page.
<script lang="ts">
export let onClick: () => void;
export let label: string;
</script>
<button class="fb-btn" on:click={onClick}>{label}</button>Runtime CSS-in-JS went too, replaced by static utility classes compiled at build.
Result
The shipped bundle dropped ~80%, the embed went iframe-free, and the feature set didn’t change. The lesson generalises: on anything you embed elsewhere, treat every kilobyte as someone else’s budget.