📊 Replace Google Analytics with warehouse analytics. Get the guide.

Log in



Refactoring RudderStack's High-performance JavaScript SDK

Blog banner


We'll send you updates from the blog and monthly release notes.

Sai kumar Battinoju

Sai kumar Battinoju

Lead Integrations Engineer

January 10, 2022

Since its initial release, we’ve refactored our JavaScript SDK multiple times, and we’ve written about how previous improvements reduced execution time from 200ms to 20ms.

Since then, the JavaScript SDK has grown in size as we’ve added support for new device-mode integrations. It became bulky enough to start impacting load times, so we recently introduced a new, optimized version of the SDK.

Here, I’ll detail the improvements made with this refactoring, walk through our team’s decision-making process, outline the tradeoffs we considered, and showcase the results of our work.

Key improvements

To optimize the size of the SDK and improve its performance, we focused on three key items:

  • Freeing the SDK of all integrations code upon build.
  • Clearing technical debt
  • Replacing third-party package dependencies

Freeing the SDK of integrations code upon build

Instead of statically importing device-mode integration modules into the core module, the integration modules are now built into independent plugins (scripts) that can be readily loaded on the client-side. Once the `load` API of the SDK is called, the necessary destination integrations are identified from the source configuration (pulled from the control plane) and their plugins are asynchronously loaded one after another from the hosted location*. After a timeout, the successfully loaded integration modules are initialized to proceed with the events forwarding.

*The hosted location defaults to RudderStack’s CDN. In the case of a custom hosted location, this can be overridden via the 'destSDKBaseURL' option in the 'load' call. Additionally, SDK determines this URL based on the script tag that adds the SDK on the website (provided the file name is still “rudder-analytics.min.js”).

Clearing technical debt

We removed as much bloat from the SDK as possible. This included dead, redundant, and deprecated code along with deprecated auto-track functionality.

Replacing third-party package dependencies

Wherever possible, we replaced third-party package dependencies with lighter ones. A few cases required custom implementations in order to achieve the results we were looking for.

Why did we decide on this approach?

By design, all the device-mode integrations are independent of each other, so it didn’t make sense to bind everything together as a single piece. Moreover, because each customer will only connect a subset of device-mode integrations to their JS/web source, loading only the necessary integrations on their site is the ideal scenario. These improvements also involved minimal changes to our SDK and processes when compared to other alternatives.

One alternative we considered was to dynamically build the SDK with necessary integrations when the request is made to https://cdn.rudderlabs.com/v1.1/rudder-analytics.js/<write key>. Using this approach, the device-mode integrations are packaged with the core SDK and delivered based on the write key provided in the URL.

We saw a few disadvantages to this approach:

  • CDN costs would increase because we would have to cache a different version of the SDK for every write key
  • We wouldn’t be able to take advantage of browser caching across various websites the user visits
  • Migrating existing users would be challenging

What tradeoffs did we have to make?

Fortunately, this refactoring didn’t involve any major tradeoffs, but there are two worth noting:

  • CDN Costs: Hosting all of the individual device-mode integration SDKs means increased CDN costs. Luckily, the additional cost is not a significant burden.
  • Migration costs: To make migrating to v1.1 worthwhile for our customers, we knew we needed to (1) introduce significant performance improvements over v1, and (2) make migrating as easy as possible. We were able to introduce significant improvements, which I’ll highlight below, and we worked to make migration as painless as possible. In most cases, migration is complete in a few simple steps, which we documented in a migration guide to help customers with all their deployment scenarios.

Problems we had to solve

In v1, all the integrations were exported from their module as the default type. We had to convert all of them to named exports for them to be dynamically loaded. See the below example:

Default type

import Amplitude from "./browser";
export default Amplitude;

Named export

import Amplitude from "./browser";
export { Amplitude };

Additionally, we had to write a script to build all the individual integrations in one go. This is what allows us to deploy the integrations along with the core SDK.

Results of the refactoring

Our new SDK is lighter and faster than the previous version. To put it into numbers:

  • We reduced the SDK size by 70%. (114 KB to 34 KB)
  • SDK download times are 80% faster (9.44 ms to 1.96ms)
  • Script evaluation times are 28% faster (86 ms to 63 ms)

Check out the PR for the refactoring on Github.

Sai kumar Battinoju


Sai kumar Battinoju

Lead Integrations Engineer



We'll send you updates from the blog and monthly release notes.

Get Started Image

Get started today

Start building smarter customer data pipelines today with RudderStack. Our solutions engineering team is here to help.