Back to Work

Cost-Optimizing Embeddable JavaScript: From $4k/month to Under $100

How we eliminated 99% of Kubernetes traffic and reduced CDN costs by 97% through strategic architecture changes—while scaling from 80M to 220M+ monthly requests.

The Problem: Every Page View Hit Our API

We had a JavaScript widget that customers embedded on their websites. The embed code was simple—a single script tag that loaded our functionality. But under the hood, every page view on every customer site triggered a request to our Laravel API running on Kubernetes.

Each request meant:

  • Full Laravel framework bootstrap
  • License validation queries against the database
  • Dynamic JavaScript generation based on customer configuration
  • A 2.7MB JavaScript file sent over the wire

At ~3,000 requests per second to our API, 99.9% of our Kubernetes cluster traffic was just serving this embedded JavaScript. The infrastructure was wildly over-provisioned for what was essentially a static file serving problem.

The Hidden Cost Multiplier

It got worse. Each JavaScript load also triggered two additional requests to fetch supplementary JSON configuration files from S3 via CloudFront CDN. So those ~25-30 million monthly API requests translated to 80-120 million CDN requests for the JSON files.

Metric Before After
API Requests/sec ~3,000 <30
CDN Requests/month 80-120M 220M+ (with caching)
JS Bundle Size 2.7MB <600KB
CDN Costs/month ~$4,000 <$100
JS Load Time ~1.2 seconds 20-50ms (cached)
K8s Traffic Reduction 99%

The Architecture Before

The original flow looked like this:

Customer Website → API (Laravel/K8s)
                     ↓
            License Validation (MySQL)
                     ↓
            Generate JS dynamically
                     ↓
            Return 2.7MB JavaScript
                     ↓
            Browser loads JS
                     ↓
            2x JSON requests → CloudFront → S3

Every single page view repeated this entire process. With millions of daily visitors across all customer sites, the costs were staggering.

The Solution: Pre-Build to S3 + Smart CDN

The insight was simple: the JavaScript was only "dynamic" based on the license configuration. But license configurations don't change on every request—they change maybe once a month. We were doing dynamic generation for what was effectively static content.

Step 1: Pre-Build Per-License JavaScript Files

Instead of generating JavaScript on every request, we pre-built customer-specific bundles and pushed them to S3:

// Build pipeline pseudocode
foreach ($licenses as $license) {
    $config = $license->getConfiguration();
    $bundle = $this->bundler->build($config);

    Storage::disk('s3')->put(
        "embeds/{$license->key}/widget.js",
        $bundle,
        ['CacheControl' => 'max-age=31536000'] // 1 year
    );
}

When a license configuration changed, we simply rebuilt and replaced that specific file.

Step 2: CDN Cost Analysis

With files now on S3, we needed a CDN. But not all CDNs are created equal, especially at high volume.

AWS CloudFront was our initial choice since we were already on AWS. The problem? Dual bandwidth charges. You pay for data transfer out of S3 to CloudFront, then again for data transfer from CloudFront to users. At 80M+ requests per month, this added up fast.

After evaluating alternatives, we switched to Bunny.net:

  • Flat-rate pricing per GB instead of per-request fees
  • No origin transfer fees when pulling from S3
  • Global edge network with PoPs in 100+ locations
  • Perma-Cache feature for truly static assets

Step 3: Aggressive Browser Caching

The final piece was browser-level caching. Since our JavaScript bundles were now versioned and immutable, we could set aggressive cache headers:

Cache-Control: public, max-age=604800

One week was the sweet spot—long enough to dramatically reduce repeat requests, short enough that users would see updates within a reasonable timeframe without intervention.

CDN Cache Purging for Instant Updates

Since the CDN respected our browser cache headers, we needed a way to force updates when customers changed their widget configuration or when licenses expired. We integrated cache purging into our build pipeline:

// Triggered when license config changes
public function rebuildAndPurge(License $license): void
{
    // Rebuild the bundle with new config
    $bundle = $this->bundler->build($license->getConfiguration());

    Storage::disk('s3')->put(
        "embeds/{$license->key}/widget.js",
        $bundle
    );

    // Purge CDN cache immediately
    $this->cdn->purge("embeds/{$license->key}/*");
}

This meant when a customer updated options on their dashboard or when a license expired, end-users would receive the updates on their next page load rather than waiting for the cache to naturally expire.

The impact was immediate. Our 80M monthly CDN requests dropped to approximately 30M—the rest were served directly from browser cache. As traffic grew, the caching efficiency meant we could handle 220M+ monthly requests with the same infrastructure. Without browser caching, we estimated this would have been 600-800M requests hitting our CDN.

Step 4: JavaScript Bundle Optimization

While rearchitecting the delivery, we also tackled the bundle size. 2.7MB for a widget was excessive. Through aggressive optimization:

  • Tree shaking: Removed unused code paths
  • Code splitting: Lazy-loaded features only when needed
  • Dependency audit: Replaced heavy libraries with lighter alternatives
  • Minification + Brotli compression: Maximum compression for wire transfer

The result: 2.7MB → <600KB, a 78% reduction in bundle size.

Handling License Expiration

One challenge with pre-built files: what happens when a license expires? We implemented a cleanup system:

// License expiration handler
public function handleExpiration(License $license): void
{
    // Delete the pre-built bundle
    Storage::disk('s3')->delete("embeds/{$license->key}/widget.js");

    // Purge CDN cache for this path
    $this->cdn->purge("embeds/{$license->key}/*");

    // Replace with "expired" placeholder if needed
    Storage::disk('s3')->put(
        "embeds/{$license->key}/widget.js",
        $this->getExpiredPlaceholder()
    );
}

The New Architecture

Customer Website → CDN (Bunny.net)
                     ↓
            Edge cache hit? → Return cached JS (99% of requests)
                     ↓
            Cache miss → S3 origin fetch
                     ↓
            Return pre-built JS (<600KB)
                     ↓
            Browser caches for 1 year

License Update → Rebuild bundle → Push to S3 → Purge CDN

Cost Breakdown

The numbers tell the story:

Component Before After
Kubernetes (API servers) Heavily utilized 99% reduction
CloudFront CDN ~$4,000/mo Eliminated
Bunny.net CDN <$100/mo
S3 Storage Minimal ~$5/mo
Total Monthly Savings ~$3,900+

Key Takeaways

This project reinforced several important lessons:

  • Question "dynamic" requirements: Just because content can be dynamic doesn't mean it should be. If data changes rarely, pre-build it.
  • CDN pricing varies wildly: At scale, the difference between CDN providers can be thousands of dollars monthly. Evaluate based on your actual usage patterns.
  • Browser cache is free: Aggressive caching with proper cache-busting is the cheapest CDN there is.
  • Bundle size matters: A 78% reduction in JS size means 78% less bandwidth, 78% faster loads, and happier users.
  • Measure before optimizing: We only discovered 99.9% of our K8s traffic was embeddable JS by actually measuring traffic patterns.
Key Takeaway

The best request is the one you never have to serve. The second best is one you serve from cache.

The Performance Win: 1.2 Seconds to 20ms

Beyond cost savings, the user experience improvement was dramatic. The original architecture required:

  • PHP bootstrap (~200ms)
  • Laravel framework initialization (~150ms)
  • Database queries for license validation (~100ms)
  • Dynamic JavaScript generation (~50ms)
  • Transfer of 2.7MB unoptimized payload (~700ms on average connections)

Total: ~1.2 seconds before the widget even started executing.

After optimization:

  • First load (CDN cache miss): 200-300ms — S3 origin fetch + CDN caches it
  • Subsequent loads (CDN cache hit): 20-50ms — served from edge
  • Repeat visitor (browser cache): <5ms — no network request at all

That's a 96% reduction in load time for first-time visitors and effectively instant loading for returning visitors. For a widget that loads on every page view, this compounds into a significantly better user experience across millions of page loads.

Results at Scale

After implementing these changes, the system scaled effortlessly. What started as 80M monthly requests (with ~$4k/month in CDN costs) grew to 220M+ monthly requests—and costs stayed under $100/month. Without the browser caching layer, that 220M would have been an estimated 600-800M requests hitting our infrastructure.

More importantly, the Kubernetes cluster that was previously dedicated to serving JavaScript could be rightsized for actual application logic. The 99% reduction in traffic meant we could reduce nodes, save on compute costs, and improve reliability for the endpoints that actually needed dynamic processing.

Alex McGlothlin

Senior Software Engineer specializing in Laravel, system architecture, and high-traffic infrastructure. 18+ years of experience building scalable solutions.