---
title: Self-host the player
description: Serve the Video.js HTML player from your own origin for offline, air-gapped, or restricted-network deployments
---

import Aside from '@/components/Aside.astro';
import DocsLink from '@/components/docs/DocsLink.astro';

By default, the <DocsLink slug="how-to/installation">installation</DocsLink> flow loads the player from a public CDN. For offline, air-gapped, or locked-down deployments, you can serve everything from your own origin instead. Pick one of the options below.

## Bundle a single file (recommended)

Install the package, put the player's imports into an entry file, then let any bundler produce one self-contained module you can host anywhere. For the default video player, that's:

```js title="player.js"
import '@videojs/html/video/player';
import '@videojs/html/video/skin';
```

```bash
npm install @videojs/html
npx esbuild player.js --bundle --format=esm --minify --sourcemap --outfile=public/player.js
```

Load the output with `type="module"`:

```html
<script type="module" src="/player.js"></script>
```

You get one file (plus a sourcemap), no runtime requests to a CDN, and only the features you import. Any bundler works (Vite, Rollup, webpack) as long as the output stays ESM.

## Mirror the prebuilt CDN files

To skip the bundler, copy the prebuilt CDN bundle to your server as-is.

<Aside type="caution" title="Copy the whole cdn/ directory">
The CDN entry files like `video.js` and `media/hlsjs-video.js` are not standalone. They import shared, content-hashed chunks (`default-*.js`, `ui-*.js`, and more) that live alongside them. Copying a single file breaks at runtime, so mirror the entire `cdn/` directory and keep its layout intact.
</Aside>

```bash
npm install @videojs/html
mkdir -p public
cp -r node_modules/@videojs/html/cdn public/videojs
```

Then point the script tag at your copy instead of the CDN:

```html
<script type="module" src="/videojs/video.js"></script>
```

The file name matches your player: `video.js`, `audio.js`, `background.js`, and so on. Every option is present in the `cdn/` folder you copied.

Hashed chunk names change between releases, so re-copy the directory whenever you upgrade `@videojs/html`.

## HLS and other media types

HLS and other non-default media need their own module on top of the player. Match it in your self-hosted build:

- **Bundle:** add the media import to your entry file (`import '@videojs/html/media/hlsjs-video';`) and use the matching `<hlsjs-video>` element with your `.m3u8` source.
- **Mirror:** serve the media file too and add its script tag (`<script type="module" src="/videojs/media/hlsjs-video.js"></script>`).

Without the media module the player UI renders but the video never loads, with no console error.
