Home / Articles / Preventing Cumulative Layout Shift with Next.js Video Backgrounds

This article is published in English.

Preventing Cumulative Layout Shift with Next.js Video Backgrounds

Learn practical CSS and layout techniques to stop background videos in Next.js from causing Cumulative Layout Shift across devices and network speeds.

1289 words

There's a specific type of frontend bug that stays invisible while you develop under ideal conditions, but the moment you throttle your network, that background video you added starts making your page jump around like something out of a chaotic sitcom scene.

Congratulations: you've just met Cumulative Layout Shift.

Video backgrounds slip past review easily because they sit in the background, they behave fine on a fast connection, and engineers tend to treat video as a purely visual layer rather than something that participates in the page's layout.

The browser, however, doesn't share our assumptions. When an element's size is unknown at initial render time, the browser has to guess. And guesses are exactly what you don't want driving your layout decisions.

With that context out of the way, let's get into the technical details.

The Core Rule: Reserve the Space Before the Video Loads

Cumulative Layout Shift quantifies how much visible content moves around unexpectedly. The most straightforward way to avoid it is to give media elements predictable, known dimensions before their actual resources have finished downloading. Setting explicit width and height, or using the CSS aspect-ratio property, lets the browser allocate that space ahead of time during layout calculation.

Put simply: reserve room for an element before it has actually rendered.

For a hero section with a background video, a good starting point is a container with a fixed footprint, rather than letting the <video> tag itself dictate how big the hero area is.

export function VideoHero() {
  return (
    <section className="relative min-h-[70svh] overflow-hidden">
      <video
        className="absolute inset-0 h-full w-full object-cover"
        autoPlay
        muted
        loop
        playsInline
        preload="metadata"
        aria-hidden="true"
      >
        <source src="/hero-video.mp4" type="video/mp4" />
      </video>

      <div className="relative z-10 mx-auto max-w-6xl px-6 py-24">
        <h1 className="text-5xl font-bold text-white">
          Build products people remember.
        </h1>
      </div>
    </section>
  );
}

The critical part here isn't the video markup at all.

It's this single line:

min-height: 70svh;

Because of it, the hero section already has a known visual footprint before the video even begins playing.

The video itself is positioned absolutely inside that container, which means it has no way to suddenly shove the content beneath it once its resource finishes loading.

That one distinction changes everything about how stable the page feels.

Don't Let the Video Define Your Layout

A typical, problematic setup looks like this:

<video
  src="/hero-video.mp4"
  autoPlay
  muted
  loop
/>

Then someone follows up with:

video {
  width: 100%;
}

And later wonders why the page behaves inconsistently depending on connection quality or screen size.

The issue is that the browser needs to know the video's dimensions ahead of time. If a video is purely decorative background content, there's rarely a good reason for it to participate in normal document flow at all.

Instead, hand layout responsibility to the wrapping container, the div that sits around the video element.

<div style="height: 100px; width: 100%">
  <video
    src="/hero-video.mp4"
    autoPlay
    muted
    loop
  />
</div>

With this structure in place, the video can behave however it wants internally, but the surrounding div keeps all of that contained.

Use object-fit: cover for Background Video

Once the video is absolutely positioned, object-fit: cover becomes genuinely useful (a rare case of a CSS property that just works as intended).

.heroVideo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

This lets the video fill the reserved hero area completely without ever altering the container's own dimensions.

There's a minor tradeoff worth noting. The cover value will crop portions of the video whenever the viewport's aspect ratio doesn't match the source footage's aspect ratio. That's a reasonable cost to accept.

The real mistake is prioritizing pixel-perfect preservation of the source video when what the design actually requires is a stable, predictable layout.

Use a Poster as the First Visual State

One particularly effective technique is supplying a well-chosen poster image for the video element.

<video
  className="absolute inset-0 h-full w-full object-cover"
  autoPlay
  muted
  loop
  playsInline
  preload="metadata"
  poster="/images/hero-poster.webp"
  aria-hidden="true"
>
  <source src="/videos/hero.mp4" type="video/mp4" />
</video>

The poster gives visitors something deliberate to look at while the actual video is still downloading. Choose that image carefully, since it's effectively your fallback design.

More importantly, this means your layout no longer depends on the video resource being instantly available.

Treat the poster as the dependable baseline state, with the video itself layered on top as a progressive enhancement, much like a skeleton loader or spinner would work in other contexts.

If the video takes several seconds to arrive over a slow mobile connection, the page still looks intentional and finished rather than broken or half-loaded.

Watch Out for 100vh Pitfalls

Fullscreen video heroes carry another subtle trap.

In the past, developers commonly wrote:

height: 100vh;

Mobile browsers complicate this because the visible viewport height shifts as browser chrome (address bars, toolbars) appears and disappears, so 100vh doesn't behave the way you'd expect.

For layouts that need to adapt well across devices, it's usually better to reach for newer viewport units, such as:

min-height: 100svh;

or, depending on how much of the screen you want the hero to fill:

min-height: 80svh;

The short version: favor 100dvh or 100svh over the older 100vh.

Avoid Serving Desktop-Sized Video to Mobile Users

Even a page with perfect CLS can still feel painfully slow if the background video itself is huge.

This is where the performance work gets more nuanced.

A rich, cinematic clip weighing 12 MB might play beautifully on a fast desktop connection, but on a mobile device over a spotty network, that same file becomes a real liability.

For most landing pages, it makes sense to provide distinct video assets for desktop and mobile viewports.

In some cases, the right call is to skip the video entirely for certain visitors.

Respecting a user's reduced-motion preference is a good example of this:

const prefersReducedMotion =
  window.matchMedia("(prefers-reduced-motion: reduce)").matches;

Within a real-world React app, this check needs to live in a client component, and you should handle it thoughtfully so it doesn't destabilize the HTML that was rendered on the server.

The underlying rule is straightforward: performance and accessibility considerations should determine whether the video runs at all, not merely how fast it downloads.

Never Let the Video Dictate the Layout

This is the guiding principle worth keeping in mind throughout.

Your page structure should hold up fine under this sequence:

Hero container
    ↓
Poster
    ↓
Video enhancement

and never depend on this one:

Video starts loading
    ↓
Browser discovers dimensions
    ↓
Hero changes height
    ↓
Everything below moves
    ↓
Lighthouse gets angry

The browser needs to already understand your layout before the heavier media asset shows up.

That's the actual fix for CLS problems caused by video.

To verify this in practice, load your site under a throttled, slower network connection and watch how it behaves.