Use Video as a Background in Your Next.js Project

Learn how to use ImageKit to automatically compress background videos, convert them to the best format for each browser, and deliver them through a global CDN.

A video background might look great during development, but it often causes performance issues when your Next.js app reaches real users and networks.

This article shows you how to use ImageKit to automatically compress videos, convert them to the best format for each browser, and deliver them through a global CDN.

For a working example, see the GitHub repository.

Introduction

Background videos are commonly used in hero sections to add visual interest.

Unlike regular videos, background videos are typically:

  • Autoplayed
  • Muted
  • Looped

Because they start automatically and often sit behind critical UI, background videos need to load fast and stay out of the way.

The problem with background videos

The biggest challenge with background videos is the cost of running them on users' devices.

During development, you'll likely have a fast machine with a good internet connection. In production, you can't control that. Your users might be dealing with low-end mobile devices, unstable connections, or browsers that don't support your video format.

In this article, you'll create an example Next.js application with a background video and learn how to optimize it with ImageKit.

Prerequisites

Before you start, make sure you have:

Set up the project

Scaffold your Next.js app and install the @imagekit/next:

npx create-next-app@latest nextjs-background
cd nextjs-background
npm install @imagekit/next
npm run dev

When prompted with Would you like to use the recommended Next.js defaults?, select Yes.

The dev server starts at http://localhost:3000.

Add video to the project

  1. Open the project in your favorite IDE
  2. Download the ocean video
  3. Place it in the public directory
  4. Rename it bg-video.mp4

Create a page with a background video

Update app/page.tsx with a basic background video:

import Link from 'next/link';

export default function Home() {
  return (
    <div className="relative w-full h-screen overflow-hidden">
      <video
        autoPlay
        muted
        loop
        playsInline
        preload="none"
        poster="/bg-video-poster.jpg"
        className="absolute inset-0 w-full h-full object-cover"
      >
        <source src="/bg-video.mp4" type="video/mp4" />
        Your browser does not support the video tag.
      </video>

      <div className="absolute inset-0 bg-black/40">
        <div className="relative z-10 flex flex-col items-center justify-center h-full text-center text-white space-y-6 px-4">
          <h1 className="text-5xl md:text-7xl font-bold tracking-tight">
            Welcome to Our Site
          </h1>
          <p className="text-xl md:text-2xl max-w-2xl text-gray-200">
            Experience stunning visuals with our video background hero section
          </p>
          <Link
            href="/imagekit"
            className="bg-white text-black px-6 py-3 rounded-lg font-semibold hover:bg-gray-200 transition-colors"
          >
            Visit ImageKit version
          </Link>
        </div>
      </div>
    </div>
  );
}

Add a still frame from your video to public/bg-video-poster.jpg to use as the poster. This shows while the video loads and acts as a fallback if the video fails.

Screenshot of the page with the local video background
Screenshot of the page with the local video background

Understand the video attributes

For the video element, we use six attributes that are all required or commonly recommended for background video to work reliably:

  • autoPlay: the video plays automatically when the page loads
  • muted: required for autoplay to work on most mobile browsers; without this, browsers block autoplay
  • loop: video repeats continuously
  • playsInline: prevents fullscreen playback on iOS, which is essential for backgrounds
  • preload="none": Good-to-have. Tells the browser not to download any video data until playback starts; without this, some browsers will eagerly fetch the entire file on page load
  • poster: Good-to-have. A still image is shown while the video loads; it also acts as a fallback if the video fails to load.

For the full list of video element attributes and browser support details, see MDN: The Video Element.

Check performance

Open browser DevTools, select the Network tab, and refresh the page. You'll notice that bg-video.mp4 is around 30 MB. Every browser and device downloads that same file, regardless of screen size or connection speed.

Running Lighthouse gives baseline metrics to compare against:

MetricBefore
FCP (First Contentful Paint)~1.2s
LCP (Largest Contentful Paint)~2.3s
Video file size~30 MB

Lighthouse report for the non-ImageKit page
Lighthouse report for the non-ImageKit page

ImageKit version

Now rebuild the page using the ImageKit SDK.

Upload your video to ImageKit

  1. Sign in to your ImageKit dashboard
  2. Open Media Library from the side menu
  3. Select + NewFile upload
  4. Upload the bg-video.mp4 from your public directory

Your video will be available at:

https://ik.imagekit.io/your_imagekit_id/bg-video.mp4
ℹ️

Replace your_imagekit_id with your actual ImageKit ID in URLs and in the following code. To find it, see your Profile section.

Configure the ImageKit provider

The @imagekit/next provides an ImageKitProvider that makes your ImageKit URL endpoint available to all components.

Create a providers wrapper:

// app/providers.tsx
'use client';

import { ImageKitProvider } from '@imagekit/next';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ImageKitProvider
      urlEndpoint={process.env.NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT!}
      transformationPosition="query"
    >
      {children}
    </ImageKitProvider>
  );
}

Wrap your layout:

// app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Add your URL endpoint to .env.local:

NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT=https://ik.imagekit.io/your_imagekit_id
ℹ️

Never hard-code your ImageKit ID or URL Endpoint directly in component files. Use environment variables so you can deploy to different environments without changing code. See the Next.js Environment Variables guide for more.

Use the ImageKit SDK in your page

Create app/imagekit/page.tsx:

import Link from 'next/link';
import { Video } from '@imagekit/next';

const urlEndpoint = process.env.NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT!;

export default function ImageKitPage() {
  return (
    <div className="relative w-full h-screen overflow-hidden">
      {/*
        Video delivers the video through ImageKit's CDN with automatic
        compression and format conversion with no extra configuration needed.

        The transformation array tells ImageKit what to do before delivery:
          w-1280  → resize to 1280px wide (reduces payload significantly)
          ac-none → remove the audio track 
      */}
      <Video
        src="/bg-video.mp4"
        transformation={[{ width: 1280 }, { raw: 'ac-none' }]}
        poster={`${urlEndpoint}/bg-video.mp4/ik-thumbnail.jpg?tr=so-3`}
        autoPlay
        muted
        loop
        playsInline
        preload="none"
        className="absolute inset-0 w-full h-full object-cover"
      />

      <div className="absolute inset-0 bg-black/40">
        <div className="relative z-10 flex flex-col items-center justify-center h-full text-center text-white space-y-6 px-4">
          <h1 className="text-5xl md:text-7xl font-bold tracking-tight">
            Welcome to Our Site
          </h1>
          <p className="text-xl md:text-2xl max-w-2xl text-gray-200">
            Experience stunning visuals with our video background hero section
          </p>
          <Link
            href="/"
            className="bg-white text-black px-6 py-3 rounded-lg font-semibold hover:bg-gray-200 transition-colors"
          >
            Visit non-ImageKit version
          </Link>
        </div>
      </div>
    </div>
  );
}

The Video component takes a src relative to your media library and a transformation array. The transformations are applied server-side by ImageKit before delivery. You don't need to pre-process anything locally.

The poster derives its URL from the same NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT environment variable you already configured, so it works across all your deployment environments without code changes.

What changed

Switching from a raw file URL to Video with transformations gives you three things:

Automatic format conversion. Open the Network tab in Chrome and check the Content-Type response header for the video request. You'll see video/webm even though the URL ends in .mp4. ImageKit reads the browser's User-Agent header and delivers VP9/WebM to browsers that support it, like Chrome, and H.264/MP4 to those that don't. You don't need to specify the format or codec. ImageKit chooses the best one automatically.

Resizing via w-1280. The source video is likely 4K or 1080p. For a background video that fills a viewport, 1280px wide is usually enough and delivers a meaningfully smaller file. Adding w-1280 to the transformation array is a one-line change that reduces the payload without any visible quality difference.

Audio removal via ac-none. The video is muted, so the audio track is wasted bytes. Adding ac-none strips it server-side, cutting another 10-20% from the file size.

Compare the performance

MetricBeforeAfterChange
Video file size~30 MB~8 MB~73% reduction
FCP (First Contentful Paint)~1.2s~0.9s25% faster
LCP (Largest Contentful Paint)~2.3s~1.3s43% faster
ℹ️

Results vary based on source video and network conditions. The file size reduction comes from a combination of resizing, audio removal, and format conversion. Test with your own video to get accurate numbers for your use case.

Lighthouse report for the ImageKit page showing improved scores
Lighthouse report for the ImageKit page showing improved scores

What ImageKit does under the hood

Automatic compression

ImageKit compresses videos to reduce file size without compromising visual quality. The default quality setting is 50 on a scale of 1-100. You can adjust this globally in the ImageKit dashboard under Optimization, or override it per-URL:

// Use quality 70 instead of the default 50
transformation={[{ width: 1280, quality: 70 }, { raw: 'ac-none' }]}

Automatic format conversion

ImageKit detects which video formats the browser supports by reading the User-Agent request header. Chrome receives VP9/WebM, a more efficient format than H.264. Older versions of Safari, for example, receive MP4 since they doesn't support WebM. The URL stays the same; the delivered format changes per browser.

For the full reference on video params, codecs, and supported formats, see the ImageKit Video Optimization Guide.

Best practices

Use a fallback image

Videos can fail to load due to a slow connection, browser issues, codec problems, or user settings. Always include a poster attribute so users see something instead of a blank screen.

ImageKit can automatically generate a thumbnail from any video. Add /ik-thumbnail.jpg to the video path and pass the URL using your NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT env variable:

const urlEndpoint = process.env.NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT!;

<Video
  src="/bg-video.mp4"
  transformation={[{ width: 1280 }, { raw: 'ac-none' }]}
  poster={`${urlEndpoint}/bg-video.mp4/ik-thumbnail.jpg?tr=so-3`}
  autoPlay
  muted
  loop
  playsInline
  preload="none"
  className="absolute inset-0 w-full h-full object-cover"
/>

The so-3 transformation extracts a frame at the 3-second mark, typically past any black opening frames. The poster image shows while the video loads, giving users something to see immediately.

Respect reduced motion preferences

Some users have vestibular disorders, migraines, or motion sensitivity. Autoplay videos can be physically uncomfortable or even nauseating for these users. Most operating systems let users indicate this preference, and you can detect it with the prefers-reduced-motion media query.

When reduced motion is preferred, show the poster image instead of playing the video. The fallback uses next/image to display the ImageKit-generated thumbnail, but Next.js requires remote image domains to be explicitly allowed via remotePatterns. Add ik.imagekit.io to your next.config.ts before this works:

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'ik.imagekit.io',
      },
    ],
  },
};

export default nextConfig;

Then the reduced-motion component:

'use client';

import { useEffect, useState } from 'react';
import Image from 'next/image';
import { Video } from '@imagekit/next';

const urlEndpoint = process.env.NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT!;
const posterUrl = `${urlEndpoint}/bg-video.mp4/ik-thumbnail.jpg?tr=so-3`;

export default function AccessibleVideoBackground() {
  const [prefersReducedMotion, setPrefersReducedMotion] = useState(() => {
    if (typeof window === 'undefined') return false;
    return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  });

  useEffect(() => {
    const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
    const handleChange = (e: MediaQueryListEvent) => setPrefersReducedMotion(e.matches);
    mediaQuery.addEventListener('change', handleChange);
    return () => mediaQuery.removeEventListener('change', handleChange);
  }, []);

  return (
    <div className="relative w-full h-screen overflow-hidden">
      {prefersReducedMotion ? (
        <Image
          src={posterUrl}
          alt="Ocean waves background"
          fill
          className="object-cover"
          priority
        />
      ) : (
        <Video
          src="/bg-video.mp4"
          transformation={[{ width: 1280 }, { raw: 'ac-none' }]}
          poster={posterUrl}
          autoPlay
          muted
          loop
          playsInline
          preload="none"
          className="absolute inset-0 w-full h-full object-cover"
        />
      )}
    </div>
  );
}

This small addition makes your site accessible to a much wider audience. For more on how the media query works across operating systems and browsers, see MDN: prefers-reduced-motion.

Conclusion

Background videos require careful handling to avoid performance issues, especially on mobile devices and slower networks. The core fixes are:

  • Resize the video to match your actual display size
  • Remove the audio track (background videos don't need it)
  • Let ImageKit handle format conversion automatically (WebM for Chrome, MP4 for Safari)
  • Add a poster image as a fallback using ImageKit's auto-thumbnail feature
  • Add preload="none" to prevent the browser from eagerly downloading video data on page load
  • Respect prefers-reduced-motion for accessibility

The ImageKit SDK handles the delivery side. Upload once, and ImageKit takes care of compression, format conversion, and CDN delivery.

Try this experiment

Open the example repo, then in DevTools select the Network tab, open the throttling dropdown, and select Slow 4G. Reload both pages and compare the load times and video file sizes.

Check the Content-Type response header on the ImageKit video; you'll see video/webm in Chrome, confirming automatic format conversion is working.