How to Fix Autoplay Video in Next.js

Learn why browsers block autoplay and how to fix it, then reduce your video file size by up to 98% using ImageKit's real-time video optimization API.

Autoplay videos are excellent for grabbing attention, but getting them to play reliably across all browsers requires the right attributes and optimization strategy.

What we'll cover:

  1. Why browsers block autoplay and the three attributes that fix it.
  2. How to reduce video file size by up to 98% using ImageKit.
  3. Building hero backgrounds and scroll-triggered playback controls.
ℹ️

You can find the complete working code in our Next.js Video Autoplay repository. All examples below are drawn directly from this project.

Understanding why autoplay fails

Modern browsers enforce strict autoplay policies. The rule to remember is videos must be muted to autoplay.

ConditionChromeSafariiOS Safari
Unmuted videoBlockedBlockedBlocked
Muted videoWorksWorksWorks
Muted + playsInlineWorksWorksWorks

Three attributes make autoplay work everywhere:

  • autoPlay — tells the browser to start playing immediately
  • muted — satisfies the "no surprise audio" policy
  • playsInline — prevents iOS from playing videos in fullscreen

Now that you understand how it works, let's build it!

Step 1: Basic AutoplayVideo component

Here's a simple component that works across all modern browsers. It includes all three required attributes (autoPlay, muted, and playsInline) so autoplay works out of the box on every platform.

// components/AutoplayVideo.tsx
'use client';

interface AutoplayVideoProps {
  src: string;
  poster?: string;
  className?: string;
}

export function AutoplayVideo({
  src,
  poster,
  className = '',
}: AutoplayVideoProps) {
  return (
    <video
      src={src}
      autoPlay
      muted
      playsInline
      loop
      poster={poster}
      className={className}
    />
  );
}

Video autoplaying on page load
Video autoplaying on page load

This works with any video URL. But a 60MB hero video will still kill your page load.

Step 2: Optimizing video delivery with ImageKit

You could manually encode videos with ffmpeg. Resize them. Strip audio. Convert formats. Then re-encode every time you change dimensions or quality settings.

Or you could do it with URL parameters.

ImageKit's Video API transforms videos on the fly. Upload once, then resize, compress, and convert by changing the URL.

Here's a video hosted on ImageKit:

https://ik.imagekit.io/your_imagekit_id/hero.mp4

Resize and optimize

Add a width parameter to resize it:

https://ik.imagekit.io/your_imagekit_id/hero.mp4?tr=w-1280
ParameterWhat it does
w-1280Resize to 1280px width (height adjusts automatically)

ImageKit also applies default format and quality optimizations automatically. It serves WebM to browsers that support it, like Chrome, and MP4 to others like Safari, which typically saves 30-50% on file size.

The default compression quality is set to 50% in the dashboard settings and works well for most use cases.

The difference

Just resizing and letting ImageKit handle format conversion drops the file size dramatically:

MetricOriginalOptimizedImprovement
File Size63.76 MB0.99 MB98% smaller
Load Time273 ms34 ms87% faster

Left: Original 4K video (63.76 MB). Right: ImageKit optimized (0.99 MB)
Left: Original 4K video (63.76 MB). Right: ImageKit optimized (0.99 MB)

Strip audio for background videos

For autoplay videos used as hero backgrounds where audio isn't needed, you can strip the audio track entirely using the ac-none parameter:

https://ik.imagekit.io/your_imagekit_id/hero.mp4?tr=w-1280,ac-none
ParameterWhat it does
ac-noneStrip the audio track completely

Removing the audio codec guarantees the video is muted at the file level and shaves off additional bytes. This is especially useful for background videos and hero sections where audio would never play.

ImageKit has a free tier that covers small projects. You can connect your own S3 bucket or upload directly to the Media Library. Sign up here to try it.

ℹ️

The demo repository includes an OptimizationComparison component that shows real-time before/after metrics.

Setting up the SDK

Install the ImageKit Next.js SDK:

npm install @imagekit/next

Create a providers wrapper with your ImageKit URL endpoint:

// 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>
  );
}

Updated AutoplayVideo with ImageKit

With the SDK configured, update the component to use ImageKit's Video component. Here's what this implementation does:

  • Uses ImageKit's Video component for built-in optimization and format negotiation.
  • Generates a thumbnail automatically by appending /ik-thumbnail.jpg to the video path, which extracts the first frame as a static preview image.
  • Combines preload="none" with the generated poster to prevent the video from downloading until playback, saving bandwidth while still showing a preview.
// components/AutoplayVideo.tsx
'use client';

import { Video, buildSrc } from '@imagekit/next';

interface AutoplayVideoProps {
  path: string;
  transformation?: Array<Record<string, string | number>>;
  className?: string;
}

export function AutoplayVideo({
  path,
  transformation = [{ width: 1280, audioCodec: 'none' }],
  className = '',
}: AutoplayVideoProps) {
  // Generate a thumbnail from the first frame of the video
  const poster = buildSrc({
    urlEndpoint: process.env.NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT!,
    src: `${path}/ik-thumbnail.jpg`,
    transformation: [{ width: 1280 }],
  });

  return (
    <Video
      src={path}
      transformation={transformation}
      autoPlay
      muted
      playsInline
      loop
      poster={poster}
      preload="none"
      className={className}
    />
  );
}

Usage:

<AutoplayVideo
  path="/hero.mp4"
  transformation={[{ width: 1280, audioCodec: 'none' }]}
/>
ℹ️

You can also extract a thumbnail from a specific time point using the so (start offset) parameter. For example, ?tr=so-5 on the thumbnail URL grabs the frame at the 5-second mark. See the thumbnail docs for more options.

Step 3: Background video pattern

A background video differs from a standard autoplay component in two ways: it fills the entire viewport behind your content, and it should always have its audio stripped since users will never hear it. Here's a reusable component that wraps our AutoplayVideo with a full-screen layout and dark overlay:

// components/BackgroundVideo.tsx
'use client';

import { AutoplayVideo } from './AutoplayVideo';

interface BackgroundVideoProps {
  path: string;
  transformation?: Array<Record<string, string | number>>;
  children: React.ReactNode;
}

export function BackgroundVideo({ path, transformation, children }: BackgroundVideoProps) {
  return (
    <section className="relative h-screen w-full overflow-hidden">
      <div className="absolute inset-0 z-0">
        <AutoplayVideo
          path={path}
          transformation={transformation}
          className="h-full w-full object-cover"
        />
      </div>
      <div className="absolute inset-0 z-10 bg-black/50" />
      <div className="relative z-20 h-full flex items-center justify-center">
        {children}
      </div>
    </section>
  );
}

Since background videos should never have audio, use ac-none to strip the audio track:

<BackgroundVideo
  path="/hero.mp4"
  transformation={[{ width: 1280, raw: 'ac-none' }]}
>
  <h1 className="text-5xl font-bold text-white">Your Headline</h1>
</BackgroundVideo>

Full-screen hero with autoplaying video background
Full-screen hero with autoplaying video background

Creating loops from longer videos

If your source video is 30 seconds but you only need a 5-second loop, trim it server-side instead of downloading the whole file:

<BackgroundVideo
  path="/hero.mp4"
  transformation={[{
    width: 1280,
    raw: 'ac-none,so-2,du-5',
  }]}
>
  <h1>Your Headline</h1>
</BackgroundVideo>

The so parameter sets where the clip begins, and du controls how long it plays. This extracts a 5-second segment starting at the 2-second mark without re-encoding locally, reducing bandwidth for background loops.

Trimmed video showing 5 second duration in the browser player
Trimmed video showing 5 second duration in the browser player

Step 4: Scroll-triggered playback

If you have multiple videos on a page, loading them all at once wastes bandwidth. Using react-intersection-observer, you can play videos only when they're visible.

The Intersection Observer API watches for a video element to enter the viewport. Once it crosses the threshold we set, it starts playback. When the user scrolls past and the video leaves the viewport, it pauses automatically.

Install the package:

npm install react-intersection-observer

Now let's create the component:

// components/ScrollVideo.tsx
'use client';

import { useRef, useEffect, useState } from 'react';
import { buildSrc } from '@imagekit/next';
import { useInView } from 'react-intersection-observer';

interface ScrollVideoProps {
  path: string;
  transformation?: Array<Record<string, string | number>>;
  className?: string;
  threshold?: number;
}

export function ScrollVideo({
  path,
  transformation = [{ width: 1280 }],
  className = '',
  threshold = 0.5,
}: ScrollVideoProps) {
  const videoRef = useRef<HTMLVideoElement>(null);
  const { ref: containerRef, inView } = useInView({ threshold });
  const [isBlocked, setIsBlocked] = useState(false);

  const urlEndpoint = process.env.NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT!;

  const src = buildSrc({ urlEndpoint, src: path, transformation });

  const poster = buildSrc({
    urlEndpoint,
    src: `${path}/ik-thumbnail.jpg`,
    transformation: [{ width: 1280 }],
  });

  useEffect(() => {
    const video = videoRef.current;
    if (!video) return;

    if (inView) {
      video.play().catch((error) => {
        console.warn('Autoplay blocked:', error);
        setIsBlocked(true);
      });
    } else {
      video.pause();
    }
  }, [inView]);

  if (isBlocked) {
    return (
      <div className={className}>
        <img src={poster} alt="" className="h-full w-full object-cover" />
      </div>
    );
  }

  return (
    <div ref={containerRef} className={className}>
      <video
        ref={videoRef}
        src={src}
        muted
        playsInline
        loop
        poster={poster}
        preload="metadata"
        className="h-full w-full object-cover"
      />
    </div>
  );
}

Usage:

<ScrollVideo
  path="/product-demo.mp4"
  transformation={[{ width: 800 }]}
  className="aspect-video"
/>

Mobile view showing video loading only when scrolled into view
Mobile view showing video loading only when scrolled into view

ℹ️

For tall vertical videos on mobile, the default threshold={0.5} may never trigger because the video is taller than the screen. Pass threshold={0.1} to start playback as soon as 10% enters the viewport.

Video still not playing?

Check these common issues.

1. iOS goes fullscreen instead of playing

You're missing playsInline. iOS requires this attribute to play videos inline. Without it, iOS tries to open the native fullscreen player and quietly fails.

2. Video works sometimes

Check if the iPhone's battery icon is yellow. iOS blocks all autoplay in Low Power Mode. No code can override this. Provide a poster fallback so users still see something.

3. Browser thinks your video has audio

Your video might look silent but still contain an audio track. Use audioCodec: 'none' in your transformation or ac-none in the URL to strip the audio codec entirely.

4. First load is slow

ImageKit caches transformed videos after the first request. The initial transformation may take a few seconds for large files. For production, request your video URLs once before going live. Learn more about first-time request processing.

Conclusion

Getting autoplay to work is only part of the solution. You also need it to load quickly. By combining the right attributes with ImageKit's real-time optimization, you get fast, reliable autoplay that works everywhere.

Sign up for a free ImageKit account and test the ac-none transformation on your own hero section.