How to Add Video to Your Astro Site with ImageKit
If you've been anywhere near web development Twitter (or X, or Bluesky, or whatever we're calling it this week) in the last few days, you've probably seen the news: Cloudflare has acquired The Astro Technology Company. The team behind one of the most interesting web frameworks to emerge in recent years is now part of the connectivity cloud giant.
For those of us who've been building with Astro, this is genuinely exciting news. Not "exciting" in the Silicon Valley way where a company gets acqui-hired and the product slowly dies, but actual excitement. Cloudflare has committed to keeping Astro open source, MIT-licensed, and platform-agnostic. Fred Schott, Astro's CEO, wrote that joining Cloudflare means the team can finally stop worrying about building a business on top of Astro and focus 100% on the framework itself.
The timing is particularly interesting because Astro 6 just hit beta, bringing a redesigned development server, better runtime support, and improved performance. With Cloudflare's resources behind it, Astro is positioned to become the definitive framework for content-driven websites—and content-driven websites almost always need video.
So let's talk about how to implement video in Astro, specifically using ImageKit's video API to handle the heavy lifting of transcoding, streaming, and optimisation.
See it in action
If you'd rather jump straight to the code or see the finished result:
- Live demo: https://elsmore.me/imagekit-astro-video-streaming/
- Source code: https://github.com/ukmadlz/imagekit-astro-video-streaming
Clone it, deploy it, tear it apart—it's all there for you to play with. Now let's dig into the details.
Table of Contents
- Why Astro for video content?
- Setting up an Astro project
- Basic video with HTML5
- Creating a reusable Video component
- Adding Video.js for advanced playback
- Adaptive Bitrate Streaming in Astro
- Optimising video with ImageKit
- Lazy loading and performance
- Persisting video across page transitions
- Conclusion
Why Astro for video content?
Astro was born in 2021 out of frustration with a web that had become obsessed with shipping JavaScript to the browser for everything. The prevailing wisdom was that every website should be architected as an application—rendered client-side, hydrated, and heavy with JavaScript bundles.
Astro took a different approach: ship HTML by default, JavaScript only when necessary.
This philosophy—what Astro calls "Islands Architecture"—is perfect for video-heavy content sites. Your video player can be an interactive island while the rest of your page (navigation, text content, footer) remains static HTML. The result? Faster initial page loads, better Core Web Vitals scores, and happier search engines.
Sites like Porsche, IKEA, and OpenAI use Astro. Platforms like Webflow and Wix have built on it. And now with Cloudflare's backing, performance-focused content sites have a framework with serious long-term support.
Setting up an Astro project
Let's start from scratch. If you've got Node.js 18.17.0 or later installed, creating a new Astro project is straightforward:
npm create astro@latest my-video-site
The CLI will ask you a few questions. For this tutorial, I'd recommend:
- Template: Empty
- TypeScript: Yes (strict)
- Dependencies: Yes
Navigate into your project and start the development server:
cd my-video-site
npm run dev
Your site is now running at http://localhost:4321/. Yes, 4321—Astro counts down rather than up. It's a small thing, but I appreciate the personality.
Basic video with HTML5
The simplest way to add video to an Astro page is exactly the same as any HTML page—the <video> element just works:
---
// src/pages/index.astro
---
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Video Demo</title>
</head>
<body>
<h1>Welcome to my video site</h1>
<video
src="https://ik.imagekit.io/ikmedia/example_video.mp4"
width="640"
height="360"
controls
>
Your browser does not support the video tag.
</video>
</body>
</html>
That's it. No JavaScript shipped to the client. No framework overhead. Just HTML and a video file.
Want multiple format sources for browser compatibility? Same as regular HTML:
<video width="640" height="360" controls>
<source src="https://ik.imagekit.io/ikmedia/example_video.mp4?tr=f-webm" type="video/webm">
<source src="https://ik.imagekit.io/ikmedia/example_video.mp4?tr=f-mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
Notice those URL parameters? That's ImageKit transforming the video format on the fly. One source file, multiple formats, no manual transcoding required.
Creating a reusable Video component
In Astro, components are .astro files with a frontmatter section (between the --- fences) and a template section. Let's create a reusable Video component:
---
// src/components/Video.astro
interface Props {
src: string;
width?: number;
height?: number;
poster?: string;
controls?: boolean;
autoplay?: boolean;
muted?: boolean;
loop?: boolean;
class?: string;
}
const {
src,
width = 640,
height = 360,
poster,
controls = true,
autoplay = false,
muted = false,
loop = false,
class: className,
} = Astro.props;
---
<video
src={src}
width={width}
height={height}
poster={poster}
controls={controls}
autoplay={autoplay}
muted={muted}
loop={loop}
playsinline
class={className}
>
<slot>Your browser does not support the video tag.</slot>
</video>
<style>
video {
max-width: 100%;
height: auto;
}
</style>
Now you can use it anywhere in your site:
---
// src/pages/index.astro
import Video from '../components/Video.astro';
---
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Video Demo</title>
</head>
<body>
<h1>Product Demo</h1>
<Video
src="https://ik.imagekit.io/ikmedia/example_video.mp4"
poster="https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=so-3"
width={800}
height={450}
/>
</body>
</html>
The component is pre-rendered at build time. Zero JavaScript sent to the browser. The video element works natively.
Adding Video.js for advanced playback
Native HTML5 video covers most use cases, but sometimes you need more. Custom controls, adaptive bitrate streaming, analytics integration—this is where Video.js comes in.
The catch with Astro is that Video.js needs to run in the browser. This is where Astro's Islands Architecture shines—we can add interactivity only where we need it.
First, install Video.js:
npm install video.js
Now create a Video.js component. Since Video.js needs client-side JavaScript, we'll use a framework integration. Astro supports React, Vue, Svelte, Solid, and more. Let's use vanilla JavaScript with Astro's <script> tag:
---
// src/components/VideoPlayer.astro
interface Props {
src: string;
poster?: string;
width?: number;
height?: number;
}
const { src, poster, width = 640, height = 360 } = Astro.props;
const playerId = `video-${Math.random().toString(36).substr(2, 9)}`;
---
<div class="video-player-wrapper">
<video
id={playerId}
class="video-js vjs-default-skin"
controls
preload="auto"
width={width}
height={height}
poster={poster}
data-src={src}
>
<source src={src} type="video/mp4" />
</video>
</div>
<script>
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
// Initialize all video players on the page
document.querySelectorAll('.video-js').forEach((element) => {
if (element instanceof HTMLVideoElement && !element.hasAttribute('data-vjs-player')) {
const player = videojs(element, {
fluid: true,
playbackRates: [0.5, 1, 1.5, 2],
});
// Cleanup on navigation (for Astro view transitions)
document.addEventListener('astro:before-swap', () => {
player.dispose();
});
}
});
</script>
<style>
.video-player-wrapper {
max-width: 100%;
}
</style>
The <script> tag in Astro components runs on the client. By importing Video.js here, Astro bundles it and ships it only when this component is used.
Adaptive Bitrate Streaming in Astro
For video-heavy sites, Adaptive Bitrate Streaming (ABS) is essential. It delivers different quality levels based on the viewer's network conditions—high quality on fast connections, lower quality (but still watchable) on slow ones.
Video.js handles both HLS and DASH protocols out of the box. Here's how to set it up with ImageKit's automatically generated streaming playlists:
---
// src/components/StreamingPlayer.astro
interface Props {
src: string;
poster?: string;
title?: string;
}
const { src, poster, title = 'Video' } = Astro.props;
// Generate HLS and DASH URLs from the source
const hlsUrl = `${src}/ik-master.m3u8?tr=sr-240_360_480_720_1080`;
const dashUrl = `${src}/ik-master.mpd?tr=sr-240_360_480_720_1080`;
---
<div class="streaming-player">
<video
id="streaming-video"
class="video-js vjs-default-skin vjs-big-play-centered"
controls
preload="auto"
poster={poster}
data-hls={hlsUrl}
data-dash={dashUrl}
>
<!-- HLS source (Safari native, others via Video.js) -->
<source src={hlsUrl} type="application/x-mpegURL" />
<!-- Fallback MP4 for older browsers -->
<source src={`${src}?tr=f-mp4`} type="video/mp4" />
</video>
</div>
<script>
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
const videoElement = document.getElementById('streaming-video') as HTMLVideoElement;
if (videoElement) {
const player = videojs(videoElement, {
fluid: true,
html5: {
vhs: {
overrideNative: true, // Use Video.js HLS instead of native
},
},
});
// Quality selector (if you add the plugin)
player.on('loadedmetadata', () => {
console.log('Stream loaded:', player.currentSource());
});
document.addEventListener('astro:before-swap', () => {
player.dispose();
});
}
</script>
<style>
.streaming-player {
max-width: 100%;
margin: 0 auto;
}
.video-js {
width: 100%;
aspect-ratio: 16 / 9;
}
</style>
The magic here is in the ImageKit URLs. The /ik-master.m3u8 suffix tells ImageKit to generate an HLS playlist, while tr=sr-240_360_480_720_1080 specifies which quality levels to include. ImageKit handles all the transcoding and segment generation automatically.
Use it like this:
---
import StreamingPlayer from '../components/StreamingPlayer.astro';
---
<StreamingPlayer
src="https://ik.imagekit.io/demo/sample-video.mp4"
poster="https://ik.imagekit.io/demo/sample-video.mp4/ik-thumbnail.jpg"
title="Product Demo"
/>
Optimising video with ImageKit
ImageKit's video API does more than just serve files. Here are the transformations that make the biggest difference for Astro sites:
Resize to match your layout
Don't send 4K video to a 640px container:
https://ik.imagekit.io/ikmedia/example_video.mp4?tr=w-640,h-360
This transformation happens on ImageKit's servers. Your users download a smaller file, your bandwidth bills go down, and videos start playing faster.
Automatic format selection
Let ImageKit figure out the best format for each browser:
<Video src="https://ik.imagekit.io/ikmedia/example_video.mp4?tr=f-auto" />
WebM for browsers that support it (better compression), MP4 for everything else.
Generate thumbnails from the video
No need to create and host separate poster images:
https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=so-5,w-640,h-360
This grabs a frame from 5 seconds into the video, resized to 640x360. Perfect for poster images and video galleries.
Add watermarks and overlays
Protect your content or add branding:
https://ik.imagekit.io/ikmedia/example_video.mp4?tr=l-text,i-My%20Brand,fs-32,co-white,l-end
Lazy loading and performance
For pages with multiple videos, you don't want them all loading at once. Here's a lazy-loading video component:
---
// src/components/LazyVideo.astro
interface Props {
src: string;
poster?: string;
width?: number;
height?: number;
}
const { src, poster, width = 640, height = 360 } = Astro.props;
// Generate a low-quality poster if none provided
const posterUrl = poster || `${src}/ik-thumbnail.jpg?tr=w-${width},h-${height},q-50`;
---
<div class="lazy-video" data-src={src} style={`aspect-ratio: ${width}/${height};`}>
<img
src={posterUrl}
alt="Video thumbnail"
loading="lazy"
width={width}
height={height}
/>
<button class="play-button" aria-label="Play video">
<svg viewBox="0 0 24 24" fill="currentColor" width="48" height="48">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</div>
<script>
document.querySelectorAll('.lazy-video').forEach((container) => {
const button = container.querySelector('.play-button');
const img = container.querySelector('img');
button?.addEventListener('click', () => {
const src = container.getAttribute('data-src');
if (!src) return;
const video = document.createElement('video');
video.src = src;
video.controls = true;
video.autoplay = true;
video.style.width = '100%';
video.style.height = '100%';
container.innerHTML = '';
container.appendChild(video);
});
});
</script>
<style>
.lazy-video {
position: relative;
background: #000;
cursor: pointer;
}
.lazy-video img {
width: 100%;
height: 100%;
object-fit: cover;
}
.play-button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.7);
border: none;
border-radius: 50%;
padding: 16px;
color: white;
cursor: pointer;
transition: background 0.2s;
}
.play-button:hover {
background: rgba(0, 0, 0, 0.9);
}
</style>
This shows a thumbnail and play button. The actual video only loads when clicked—no wasted bandwidth on videos users never watch.
Persisting video across page transitions
Astro 3+ introduced View Transitions, which enable smooth animations between pages. One neat feature: you can persist elements across navigations.
If you have a video playing and the user navigates to another page with the same video, you can keep it playing:
<video
src="https://ik.imagekit.io/ikmedia/example_video.mp4"
controls
transition:persist
>
</video>
The transition:persist directive tells Astro to keep this element instead of replacing it during navigation. The video continues playing without interruption.
This is fantastic for sites with persistent media players—podcasts, background music, or video courses where you don't want playback to stop as users navigate.
Conclusion
Astro's philosophy of shipping minimal JavaScript makes it an excellent choice for video content sites. The framework gives you the performance benefits of static HTML while allowing interactive islands—like video players—exactly where you need them.
With Cloudflare's acquisition, Astro's future looks more stable than ever. The team can focus entirely on the framework without the distraction of building a business model. For those of us building content-driven sites with video, that's genuinely good news.
Combine Astro's architecture with ImageKit's video API, and you get a powerful setup: automatic format optimisation, on-the-fly transcoding, adaptive bitrate streaming, and thumbnail generation—all without maintaining your own video infrastructure.
The web doesn't have to be slow. It doesn't have to ship megabytes of JavaScript for every page load. And video doesn't have to be complicated.