Videos are incredibly engaging. Over the last five years, they’ve practically taken over social media: Instagram, TikTok, LinkedIn, Twitter... you name it. Marketers and creators are racing to make video content.
But here’s the thing: making a great video is just the start. You still have to get people to actually watch it on the web, and that’s where things can get tricky. If your video delivery isn’t optimized, it can greatly impact the user experience of your website or app.
Hosting your videos on platforms like YouTube or Vimeo is perfectly fine. However, if you want total control over the user experience and your branding, you’ll need to host and stream the content yourself. And that’s exactly why optimizing videos for the web is so important.
This blog is for developers who want to stream optimized videos in React applications. We’ll cover why video optimization matters, show you the essential tools for encoding and compression, and walk through how to set up smooth streaming so your users get a seamless viewing experience.
What is Video Optimization?
Video optimization is the process of compressing and converting videos to reduce their file size while maintaining the best possible quality. This process ensures that videos load quickly and play smoothly on all devices. There are various ways to optimize videos:
- Lazy loading - Loading videos only when the user plays them.
- Video resizing - Changing the dimensions of a video on the server side to fit the application layout on the front end.
- Video format selection - Choosing the right output video format based on the video content and device.
- Video compression - Reducing the video file size while maintaining visual quality using lossy compression techniques.
- Adaptive bitrate streaming - Delivering videos at different quality levels based on the user's network speed.
You can implement some optimizations by yourself but using a specialized video CDN (content delivery network) like ImageKit lets you build a video application without worrying about the complexities of encoding, storage, caching, and delivery.
Setting up the App
Let’s go through an example project for a more hands-on experience on implementing these optimizations without using any third-party services and then compare those solutions with ImageKit.
If you already have a React project set up, you can follow along. If not, run the command below.
npm create vite@latest my-app -- --template react
This’ll set up a project with React using the Vite build tool (which is one of the recommended ways to use React now).
You can specifyreact-ts
instead ofreact
if you want to use a template that has React with TypeScript.
Switch to the newly created app directory and run npm install
to install the required dependencies. Once that’s done, you can use the npm run dev
command to run the template locally.

If you created a new app, your directory would look like this:
.
├── README.md
├── eslint.config.js
├── index.html
├── package-lock.json
├── package.json
├── node_modules
├── public
│ └── vite.svg
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── assets
│ ├── index.css
│ └── main.jsx
└── vite.config.js
We’ll start by using the <video>
tag to embed a video on the webpage.
Before that, let’s clear the default styles from App.css
& index.css
.
In your index.css
file, paste this:
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
}
body {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 6rem;
min-height: 100vh;
}
Clear the the App.jsx
file & paste this snippet instead to add a video:
import "./App.css";
function App() {
return (
<>
<video height="452" width="768" controls playsInline>
<source src="https://ik.imagekit.io/ikmedia/example_video.mp4" />
Your browser does not support the video tag...
</video>
</>
);
}
export default App;
Let’s go through the different attributes we used:
- controls: adds native video playback controls (e.g., play, skip, volume adjustment).
- playsinline: prevents videos from playing in full-screen mode on iOS devices.
We’re using an example video link from ImageKit as our source, but you can also use other links or a file stored in the assets or public directory.
The page should look like this now:

video
tagNow that we’ve setup our app, let’s review different ways to optimize the performance of our video content.
Lazy loading
Browsers load a small portion of the video before you even hit play. You can either see this in the progress bar of your video player (if it supports showing the duration of pre-loaded content) or check this in the network tab of your browser’s developer tools.

While pre-loading videos might be ideal in some cases, it impacts your website’s performance and consumes bandwidth unnecessarily if the user doesn’t even want to play a specific video. In some cases, the browser may fetch the entire video.
To prevent this, you can set the preload
attribute for the video
element to none
.
<video height="452" width="768" controls playsInline preload="none">
<source src="https://ik.imagekit.io/ikmedia/example_video.mp4" />
Your browser does not support the video tag...
</video>
When the page reloads, you’ll notice that the video thumbnail has disappeared because the browser didn’t pre-load the video or its metadata.

To show a thumbnail, you can use the poster
attribute with any image of your choice. You can either capture a screenshot or get a thumbnail at any timestamp using ffmpeg
like this:
ffmpeg -i input.mp4 -ss 00:00:5 -vframes 1 thumbnail.jpg
-i input.mp4
: specifies the input video file namedinput.mp4
.-ss 00:00:5
: seeks to 5 seconds into the video.-vframes 1
: extracts only one frame.thumbnail.jpg
: Name of the output image file.
<video
height="452"
width="768"
controls
playsInline
preload="none"
poster="./thumbnail.png"
>
<source src="https://ik.imagekit.io/ikmedia/example_video.mp4" />
Your browser does not support the video tag...
</video>
Instead of the browser pre-loading the video to show a thumbnail, using a thumbnail image leads to much better performance.
ik-thumbnail.jpg
to the video URL:https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg
To capture a thumbnail from a specific timestamp, use the
so
(start-offset) parameter:https://ik.imagekit.io/ikmedia/example_video.mp4/ik-thumbnail.jpg?tr=so-5
Here,
so-5
extracts a frame 5 seconds into the video instead of the default first frame.Video resizing
Using videos with lower resolutions helps save a lot of bandwidth for mobile users. You can use a tool like ffmpeg to manually convert your videos to lower resolution.
Download the example MP4 video from https://ik.imagekit.io/ikmedia/example_video.mp4
You can check the video resolution using either your file explorer or running ffmpeg -i input.mp4
, where input.mp4
is the name of your video file.
To reduce the resolution to a specific dimension while preserving the aspect ratio:
ffmpeg -i input.mp4 -vf scale=-1:480 -c:a copy output-480p.mp4
-vf scale=-1:480
: applies a video filter (-vf
) to scale the video.scale=-1:480
: Resizes the video to a height of 480p while maintaining the original aspect ratio.-c:a copy
: copies the audio stream without re-encoding it (faster and preserves original audio quality).
If you face this issue, you can add some padding to round off the dimension to the nearest even number by using the
vf
flag like this: -vf "scale=-1:480,pad=ceil(iw/2)*2:ih"
. Here:-
iw
: is the input width after scaling.-
ceil(iw/2)*2
: rounds up the width to the nearest even number to ensure it is divisible by 2.-
ih
: keeps the height unchanged.If you’re using ImageKit, you can use the height (h) transformation for the same result.https://ik.imagekit.io/ikmedia/example_video.mp4?tr=h-480
h-480
resizes the video to have a height of 480px while maintaining the width as per the original aspect ratio.
<video
height="452"
width="768"
controls
playsInline
preload="metadata"
poster="./thumbnail.png"
>
<!-- Mobile devices: Small screens (e.g., phones) -->
<source
src="https://ik.imagekit.io/ikmedia/example_video.mp4?tr=h-240"
media="(max-width: 480px)"
/>
<!-- Tablets and larger mobile devices -->
<source
src="https://ik.imagekit.io/ikmedia/example_video.mp4?tr=h-480p"
media="(max-width: 768px)"
/>
<!-- Small laptops and large tablets -->
<source
src="https://ik.imagekit.io/ikmedia/example_video.mp4?tr=h-720"
media="(max-width: 1024px)"
/>
<!-- Default fallback (highest quality available) -->
<source src="https://ik.imagekit.io/ikmedia/example_video.mp4" />
Your browser does not support the video tag...
</video>
Video format selection
WebM is a newer video format that provides high-quality video playback with smaller file sizes and faster loading times. However, MP4 is widely compatible across all browsers and devices. You should encode your videos in WebM for better performance and provide MP4 as a fallback for optimal performance & compatibility.
We’ll see how we can add this fallback once we understand how to convert MP4 to WebM. To convert an MP4 video to WebM, run
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -c:a libopus output.webm
-c:v libvpx-vp9
: Sets the video codec to VP9, which is commonly used for WebM.-crf 30
: Adjusts the quality (lower value = better quality). A typical range is 20–40.-c:a libopus
: Sets the audio codec to Opus, which is recommended for WebM files.output.webm
: Specifies the name of the output WebM file.
You can use the videos as shown in the snippet below. Remember to keep the WebM video above the MP4 one so that the browser checks for the WebM video first and only uses the MP4 video as a fallback.
<video
height="452"
width="768"
controls
playsInline
preload="metadata"
poster="./thumbnail.png"
>
<source src="./video.webm" />
<source src="./video.mp4" />
Your browser does not support the video tag...
</video>
ImageKit automatically selects the best format for the user depending on browser support. You don’t need to add any additional parameters or put multiple URLs like this using source
tag. Just upload an MP4 video to ImageKit, and it’ll be delivered as WebM on the same URL if that’s the best format for a user.
For example, even though the original video uploaded (https://ik.imagekit.io/ikmedia/example_video.mp4?tr=orig-true
) was in the MP4 format, I got delivered the more efficient WebM format in my Chrome browser on Mac. Some older browsers don’t support WebM, so providing MP4 as a fallback is essential.

f
) query parameter for more control over the video format. For example, to deliver the video in MP4 format.https://ik.imagekit.io/ikmedia/example_video.mp4?f=mp4
Adaptive Bitrate Streaming (ABS)
One of the biggest challenges in delivering video content online is ensuring a smooth and uninterrupted viewing experience for all users, regardless of their internet speed or device capabilities. Viewers with slow or unstable connections often face buffering, lag, and poor video quality, while those with high-speed connections may not be getting the best possible resolution due to static video delivery methods.
Adaptive Bitrate Streaming (ABS) ensures a smooth and high-quality viewing experience by dynamically adjusting the video quality based on the viewer's internet speed and device capabilities. This reduces buffering, minimizes interruptions, and optimizes bandwidth usage, delivering the best possible video quality for each user while maintaining efficiency.
ABS works by encoding a video into multiple quality levels or bitrates and splitting it into small chunks. As the video streams, the player's client dynamically selects the best quality chunk based on the viewer's current network conditions and device capabilities. If the connection is strong, higher-quality chunks are streamed; if it weakens, the player seamlessly switches to lower-quality chunks to avoid buffering.
There are 2 different protocols for using ABS: HLS (HTTP Live Streaming) & DASH (Dynamic Adaptive Streaming over HTTP). You can learn more about them here: HLS Vs. DASH: Which Streaming Protocol is Right for You?.
If you’re not using any external service like ImageKit for ABS streaming, you’ll need to process your videos beforehand to use ABS.
To generate an HLS playlist that splits the video into multiple segments and supports different resolutions (360p, 480p, 720p, 1080p):
ffmpeg -i input.mp4 \
-map 0:v:0 -map 0:a:0 -map 0:v:0 -map 0:a:0 -map 0:v:0 -map 0:a:0 -map 0:v:0 -map 0:a:0 \
-c:v h264 -profile:v main -c:a aac -ar 48000 \
-filter:v:0 scale=w=640:h=360:force_original_aspect_ratio=decrease -b:a:0 96k \
-filter:v:1 scale=w=842:h=480:force_original_aspect_ratio=decrease -b:a:1 128k \
-filter:v:2 scale=w=1280:h=720:force_original_aspect_ratio=decrease -b:a:2 128k \
-filter:v:3 scale=w=1920:h=1080:force_original_aspect_ratio=decrease -b:a:3 192k \
-var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 v:3,a:3" \
-hls_time 4 \
-master_pl_name master.m3u8 \
-hls_segment_filename output/stream_%v_%03d.ts output/stream_%v.m3u8
output
in our case) if not already present to prevent ffmpeg from erroring out in case the directory isn’t present for HLS segments.Let’s go through the options used in this command:
-map 0:v:0 -map 0:a:0 ...
- maps the first video (0:v:0
) and first audio (0:a:0
) streams 4 times for different variants.-c:v h264
: specifies H.264 as the video codec.-profile:v main
: uses the "Main" profile of H.264 (better compression and quality).-c:a aac
: specifies AAC as the audio codec.-filter:v:0 scale=w=640:h=360:force_original_aspect_ratio=decrease
: rescales the first video stream to 640x360 while preserving the aspect ratio.-b:a:0 96k
: sets the audio bitrate for the first variant to 64 Kbps.-var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 v:3,a:3"
: groups audio and video streams for different bitrate variants.-hls_time 4
: splits the video into 4-second segments.-master_pl_name master.m3u8
` : generates a master playlist file (master.m3u8
) that lists all available stream variants.-hls_segment_filename output/stream_%v_%03d.ts output/stream_%v.m3u8
: specifies the naming pattern for HLS segments.
You can see the different bitrate streams listed in the master playlist (output/master.m3u8
) like this:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=105600,RESOLUTION=640x360,CODECS="avc1.4d401e,mp4a.40.2"
stream_0.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=140800,RESOLUTION=842x474,CODECS="avc1.4d401e,mp4a.40.2"
stream_1.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=140800,RESOLUTION=1280x720,CODECS="avc1.4d401f,mp4a.40.2"
stream_2.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=211200,RESOLUTION=1920x1080,CODECS="avc1.4d4028,mp4a.40.2"
stream_3.m3u8
The playlist for each bitrate stream (eg. output/stream_0.m3u8
) shows the segmented video streams.
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:2
#EXTINF:10.000000,
stream_1_002.ts
#EXTINF:10.000000,
stream_1_003.ts
#EXTINF:10.000000,
stream_1_004.ts
#EXTINF:10.000000,
stream_1_005.ts
#EXTINF:10.000000,
stream_1_006.ts
#EXT-X-ENDLIST
Once you’ve created the playlist and video segments, you can host them on your server to use ABS.
If you don’t want the hassle of generating and hosting multiple bitrate variants for optimal ABS streaming, you can use ImageKit’s ABS capabilities. You just need to specify the required resolutions like in the following URL:
https://ik.imagekit.io/ikmedia/example_video.mp4/ik-master.m3u8?tr=sr-360_480_720_1080
Now that we have a link to our HLS manifest, let’s update our video player to support ABS.
The HTML <video>
tag works well, but it doesn’t support using ABS. We’ll use the popular video.js library for ABS support.
Let’s install the required dependencies:
npm install video.js@7.18.1 videojs-contrib-quality-levels@2.1.0 videojs-http-source-selector@1.1.6
Using the specific version 7.18.1
of video.js is recommended to ensure compatibility with the plugins used.
- videojs-contrib-quality-levels: provides an interface for accessing and managing available quality levels in a video.js player, useful for monitoring or customizing ABS playback.
- videojs-http-source-selector: uses videojs-contrib-quality-levels to offer manual user-selectable level selection options for adaptive HTTP streams.
Create a new directory components
under src
. Add a file named VideoPlayer.jsx
to it with the code below:
import { useEffect, useRef } from "react";
import videojs from "video.js";
import "video.js/dist/video-js.css";
import "videojs-contrib-quality-levels";
import "videojs-http-source-selector";
export const VideoPlayer = (props) => {
const placeholderRef = useRef(null);
const playerRef = useRef(null);
const { options, onReady } = props;
useEffect(() => {
// Make sure Video.js player is only initialized once
if (!playerRef.current) {
const placeholderEl = placeholderRef.current;
const videoElement = placeholderEl.appendChild(
document.createElement("video-js")
);
const player = videojs(videoElement, options, () => {
player.log("player is ready");
onReady && onReady(player);
});
playerRef.current = player;
// Binding to the source selector plugin in Video.js
player.httpSourceSelector();
// You can update player in the `else` block here, for example:
} else {
const player = playerRef.current;
player.autoplay(options.autoplay);
player.src(options.sources);
}
}, [options, onReady]);
// Dispose the Video.js player when the functional component unmounts
useEffect(() => {
const player = playerRef.current;
return () => {
if (player) {
player.dispose();
playerRef.current = null;
}
};
}, [playerRef]);
return <div ref={placeholderRef}></div>;
};
export default VideoPlayer;
Replace the contents of App.jsx
with
import "./App.css";
import VideoPlayer from "./components/VideoPlayer";
function App() {
const videoJsOptionsM3u8 = {
controls: true,
autoplay: false,
width: 768,
sources: [
{
src: "https://ik.imagekit.io/ikmedia/example_video.mp4/ik-master.m3u8?tr=sr-360_480_720_1080",
type: "application/x-mpegURL",
},
],
plugins: {
httpSourceSelector: {
default: "auto",
},
},
};
return (
<>
<VideoPlayer options={videoJsOptionsM3u8} />
</>
);
}
export default App;
You can learn more about VideoJS options here: Video.js Options Reference.
After making all the changes, you’ll get a video player that auto-selects the best stream provided by ImageKit & also provides different options for resolutions to the user.

Conclusion
Optimizing video content in React ensures a smooth user experience, better performance, and reduced bandwidth costs.
You can significantly improve how videos are delivered and consumed on your website by applying techniques such as preloading control, using multiple resolutions, providing multiple formats, and ABS.
Tools like ImageKit simplify this process further by automating transformations and delivering optimized videos based on user context without the hassle of doing things manually.