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 specify react-ts instead of react 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.

Initial Screen

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
Directory Tree

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:

Adding the video tag

Now 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.

Browser Pre-loading

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>
ℹ️
For an even improved experience, you can use react-lazyload that’ll only mount components when they’re visible in the viewport.

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.

No thumbnai with preload="none"

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 named input.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.

ℹ️
With ImageKit, you can generate an optimized thumbnail by appending 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).
ℹ️
Note: Some video codecs don’t work if the height and width of the video isn’t exactly divisible by 2.

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.
ℹ️
If you want to learn more about both formats and other codecs, head over to Exploring WebM vs MP4.

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.

ℹ️
You can check WebM compatibility for different platforms here: WebM video format | Can I use.
Automatic Format Optimization

ℹ️
You can still use the format (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
ℹ️
Remember to create an output directory (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

ℹ️
To learn more about the HLS playlist format, see Creating a Multivariant Playlist | Apple Developer Documentation

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

ℹ️
See the list of resolutions supported by ImageKit here: Adaptive Bitrate Streaming

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.

ABS-compatible video player

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.