AuthonAuthon Blog
debugging7 min read

Why Your Video Player Is Bloating Your Bundle (and How to Fix It)

Video.js v10 beta dropped an 88% size reduction. Here's why the old version was bloated and how to migrate to the leaner rewrite.

AW
Alan West
Authon Team
Why Your Video Player Is Bloating Your Bundle (and How to Fix It)

If you've ever cracked open your webpack bundle analyzer and seen a giant chunk labeled video.js, you know the feeling. That sinking realization that your video player alone is heavier than the rest of your app combined.

I hit this exact wall two months ago. A client's Lighthouse score was tanking on mobile, and after some digging, the culprit was obvious: video.js v8 was shipping nearly 600KB of minified JavaScript. For a page that sometimes didn't even autoplay video until the user scrolled down. Brutal.

The good news? Video.js just dropped a v10 beta that's 88% smaller than its predecessor. Let's dig into why the old version was so bloated, how they fixed it, and how you can migrate without blowing up your existing player setup.

The Root Cause: Legacy Architecture Debt

Video.js has been around since 2010. That's ancient in JavaScript years. Over time, it accumulated a lot of weight:

  • A custom UI component system that predates modern frameworks
  • A full CSS stylesheet bundled inline
  • Flash fallback code (yes, really — remnants hung around for ages)
  • A custom DOM manipulation layer that duplicated what browsers already do well
  • Middleware and plugin systems with layers of abstraction

The core problem wasn't any single feature. It was that Video.js was essentially a framework-within-a-framework. It had its own class system, its own event system, its own DOM helpers — all things that made sense in 2010 but are dead weight in 2026.

Here's what a basic v8 setup looks like for reference:

js
// v8 — the "classic" way
import videojs from 'video.js';
import 'video.js/dist/video-js.css'; // ~30KB of CSS you may not need

const player = videojs('my-video', {
  controls: true,
  autoplay: false,
  preload: 'auto',
  fluid: true
});

// Even this minimal setup pulls in ~580KB minified (before gzip)

That import drags in the entire kitchen sink. Every UI component, every legacy polyfill, every feature you're not using. There was no tree-shaking because the architecture wasn't designed for it.

The Fix: Video.js v10's Ground-Up Rewrite

The v10 beta takes a fundamentally different approach. Instead of shipping a monolith, it's been rewritten as a lean core with opt-in features. The headline stat is real: the core player dropped from roughly 580KB to around 70KB minified.

Here's what changed architecturally:

  • Native web APIs instead of custom abstractions — No more custom DOM layer. They use standard HTMLMediaElement APIs directly.
  • Tree-shakeable ESM modules — Features you don't import don't ship. Period.
  • CSS decoupled from JS — Styles are separate and optional. Bring your own design system if you want.
  • Modern baseline — No more IE11 polyfills. The target is modern evergreen browsers.

Step 1: Install the Beta

bash
npm install video.js@next
# or if you want to pin the exact beta
npm install video.js@10.0.0-beta.1

Fair warning: this is a beta. Don't deploy it to production on a site doing 10 million video plays a day. But for new projects or internal tools? Absolutely worth trying.

Step 2: Update Your Player Initialization

The new API is simpler and more explicit:

js
// v10 — lean and explicit
import { Player } from 'video.js';

const player = new Player(document.querySelector('#my-video'), {
  controls: true,
  autoplay: false,
  preload: 'auto'
});

// Core is ~70KB minified. That's it. No CSS forced on you.

Notice the shift: you're importing Player as a named export, not a default function. This is what makes tree-shaking work. The bundler can see exactly what you're using.

Step 3: Add Only What You Need

Need HLS streaming? Import it explicitly:

js
import { Player } from 'video.js';
import { HlsHandler } from 'video.js/modules/hls';

const player = new Player(document.querySelector('#my-video'), {
  controls: true,
  techOrder: [HlsHandler]
});

// Only pays the cost for what you actually use

This modular approach means your bundle only includes the code paths you actually exercise. If you're just playing a simple MP4 with basic controls, you get basic MP4 with basic controls — not an HLS stack, not ad integration hooks, not a quality selector you'll never render.

Step 4: Handle the CSS Situation

In v8, the player looked decent out of the box because it force-bundled a stylesheet. In v10, you have options:

css
/* Option A: Import their minimal base styles */
@import 'video.js/styles/base.css';

/* Option B: Just target the native elements yourself */
video {
  width: 100%;
  aspect-ratio: 16 / 9;
}

.vjs-controls {
  /* style controls however you want */
}

I actually prefer this approach. In v8, I spent more time overriding their CSS than I'd like to admit. Now the styling layer is genuinely optional.

Checking Your Bundle Impact

After migrating, verify the size difference. Here's a quick way to measure:

bash
# Check what video.js contributes to your bundle
npx esbuild your-entry.js --bundle --minify --analyze | grep video

# Or if you're on webpack, the classic:
npx webpack-bundle-analyzer dist/stats.json

On the project I migrated, the video-related JavaScript went from 187KB gzipped to about 22KB gzipped. That's not a typo. My Lighthouse performance score jumped 12 points on mobile just from this change.

What Breaks (Honestly)

Let's be real about the tradeoffs:

  • Plugins will break. If you rely on v8 plugins from the ecosystem, they won't work with v10 out of the box. The plugin API has changed significantly.
  • The component system is different. If you built custom UI components using videojs.registerComponent(), you'll need to rewrite them.
  • Some events have changed. The core events (play, pause, ended) are the same, but higher-level custom events may have shifted.
  • No more videojs() function. It's a class now. new Player() is the way.

If you've got a complex player with custom skins, multiple plugins, and ad integration, this migration is non-trivial. You'll want to wait for the stable release and for plugin authors to catch up.

Prevention: Stop Shipping What You Don't Use

This situation with Video.js is a pattern I see everywhere. Libraries start small, accumulate features for a decade, and suddenly you're shipping half a megabyte of code to play a 30-second clip.

Some rules I follow now:

  • Audit your bundle quarterly. Run npx bundlephobia before adding any new dependency. Set a size budget and stick to it.
  • Prefer libraries with ESM exports. If a library only ships CommonJS, tree-shaking is either impossible or unreliable.
  • Lazy-load heavy media components. Your video player doesn't need to be in the initial bundle if the video is below the fold.
  • Consider native first. If you just need play/pause on an MP4, you might not need a library at all. The browser's native player has gotten remarkably good.
js
// Sometimes this is all you need. Seriously.
const video = document.querySelector('video');
video.addEventListener('ended', () => showNextContent());
// The browser handles controls, fullscreen, PiP — all of it

The Video.js team deserves credit here. Rewriting a library used by millions of sites is gutsy. Most maintainers would keep bolting features onto the existing architecture. Throwing it out and starting fresh, with a clear focus on bundle size, is the right call.

I'll be watching the v10 beta closely. If you're starting a new project with video, it's worth trying now. If you're maintaining an existing v8 setup, start planning the migration but don't rush it — wait for the plugin ecosystem to catch up.

Either way, go check your bundle. You might be surprised what's hiding in there.

Why Your Video Player Is Bloating Your Bundle (and How to Fix It) | Authon Blog