How We Scaled Our Widget to Handle 1 Million Embeds
When our embeddable survey widget crossed a million active installs, the naive architecture that worked fine for the first ten thousand started showing real strain. This is how we redesigned it to scale gracefully.
The Original Architecture
Every widget load hit our API directly to fetch the survey configuration, check eligibility rules, and render the form. At low volume, this was simple and correct. At a million installs, it meant a spike in traffic every time a popular site's homepage loaded — occasionally taking down the API for unrelated customers.
Moving Config to a CDN
Survey configuration changes infrequently relative to how often it's read. We moved the widget's config payload to a CDN-cached JSON endpoint with a short TTL and cache-busting on publish. This single change removed over 90% of widget-related load from our origin servers.
Client-Side Eligibility Rules
Rules like "don't show this survey to the same visitor twice in 30 days" used to require a server round-trip on every page load. We moved this logic entirely client-side using local storage with a signed timestamp, falling back to server validation only at actual submission time.
Lazy-Loading the Render Engine
The widget script itself ships in two parts: a tiny (under 5KB) loader that decides whether to show the survey at all, and a larger rendering bundle that only downloads if the eligibility check passes. This keeps the baseline cost near-zero for the majority of page loads where the widget never actually displays.
Monitoring at This Scale
Traditional server-side monitoring tools weren't built for a system where the vast majority of "requests" never touch our servers at all — they resolve entirely from CDN cache. We instrumented client-side beaconing (a lightweight, sampled ping on a small percentage of loads) to get visibility into real-world load times and error rates across the full installed base, since CDN access logs alone don't tell you enough about the actual end-user experience.
Results
After the redesign: origin server load from widget traffic dropped by over 90%, median widget load time fell from 380ms to under 60ms, and we could scale to new installs without provisioning additional API capacity. The lesson generalizes well beyond survey widgets — anything embedded on thousands of unrelated sites needs to treat "read config" and "submit data" as fundamentally different scaling problems.