Evitar el desplazamiento acumulativo del layout con fondos de video en Next.js
Aprenda técnicas prácticas de CSS y diseño para evitar que los videos de fondo en Next.js causen desplazamientos acumulativos en el layout en diferentes dispositivos y velocidades de red.
Existe un tipo específico de error en la interfaz frontal que permanece invisible mientras se desarrolla bajo condiciones ideales, pero en cuanto se ralentiza la red, ese video de fondo que se agregó hace que la página se mueva de forma caótica, como en una escena de comedia situacional.
Felicitaciones: acaban de conocer el desplazamiento acumulativo del diseño.
Los fondos de video pasan fácilmente desapercibidos durante las pruebas porque se encuentran en segundo plano, funcionan bien con conexiones rápidas, y los ingenieros tienden a tratar el video como una capa puramente visual en lugar de algo que influye en el diseño de la página.
No obstante, el navegador no comparte nuestras suposiciones. Cuando el tamaño de un elemento es desconocido en el momento inicial de renderizado, el navegador debe adivinar. Y las conjeturas son precisamente lo que no se quiere que influya en las decisiones de diseño.
Con ese contexto aclarado, veamos los detalles técnicos.
La regla fundamental: reserve el espacio antes de que se cargue el video
Cumulative Layout Shift mide cuánto contenido visible se mueve de forma inesperada. La forma más sencilla de evitarlo es asignar a los elementos multimedia dimensiones predecibles y conocidas antes de que se hayan completado la descarga de sus recursos reales. Establecer un ancho y alto explícitos, o utilizar la propiedad CSS aspect-ratio, permite al navegador asignar ese espacio con antelación durante el cálculo del diseño.
En pocas palabras: reserve espacio para un elemento antes de que se renderice realmente.
Para una sección principal con video de fondo, un buen punto de partida es utilizar un contenedor con dimensiones fijas, en lugar de dejar que la etiqueta <video> misma determine el tamaño del área principal.
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>
);
}
La parte crucial aquí no es en absoluto el marcado del video.
Es esta única línea:
min-height: 70svh;
Debido a esto, la sección del héroe ya tiene una huella visual definida antes incluso de que comience a reproducirse el video.
El propio video está posicionado de forma absoluta dentro de ese contenedor, lo que significa que no puede colocar repentinamente el contenido debajo una vez que se haya completado la carga de sus recursos.
Esa diferencia cambia por completo la sensación de estabilidad de la página.
No dejes que el video defina tu diseño
Una configuración típica y problemática se ve así:
<video
src="/hero-video.mp4"
autoPlay
muted
loop
/>
Luego alguien añade lo siguiente:
video {
width: 100%;
}
Y más tarde se pregunta por qué la página se comporta de manera inconsistente según la calidad de la conexión o el tamaño de la pantalla.
El problema es que el navegador necesita conocer las dimensiones del video con antelación. Si un video es únicamente contenido decorativo de fondo, rara vez hay una buena razón para que participe en el flujo normal del documento.
En su lugar, delegue la responsabilidad del diseño en el contenedor que lo envuelve, es decir, el div que rodea al elemento de video.
<div style="height: 100px; width: 100%">
<video
src="/hero-video.mp4"
autoPlay
muted
loop
/>
</div>
Con esta estructura en su lugar, el video puede comportarse como desee internamente, pero el div que lo rodea mantiene todo contenido dentro de sus límites.
Use object-fit: cover para video de fondo
Una vez que el video está posicionado absolutamente, object-fit: cover resulta realmente útil (un caso raro en el que una propiedad de CSS funciona tal como se pretende).
.heroVideo {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
Esto permite que el video llene por completo el área reservada sin modificar nunca las dimensiones del propio contenedor.
Hay un pequeño compromiso que vale la pena mencionar. El valor cover recortará partes del video siempre que la relación de aspecto de la ventana no coincida con la del material de origen. Eso es un costo razonable de aceptar.
El verdadero error es dar prioridad a la preservación perfecta de píxel a píxel del video original, cuando lo que realmente requiere el diseño es un layout estable y predecible.
Utilice un póster como primer estado visual
Una técnica particularmente efectiva es proporcionar una imagen de póster bien seleccionada para el elemento de video.
<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>
El póster ofrece a los visitantes algo concreto en lo que fijarse mientras el video real sigue descargándose. Elija esa imagen con cuidado, ya que en esencia funciona como su diseño de respaldo.
Más importante aún, esto significa que su layout ya no depende de que el recurso de video esté disponible al instante.
Trate el póster como el estado base fiable, con el video en sí superpuesto como una mejora progresiva, de forma similar a cómo funcionarían un cargador de esqueleto o un spinner en otros contextos.
Si el video tarda varios segundos en cargarse por una conexión móvil lenta, la página sigue pareciendo completa e intencionada en lugar de estar rota o parcialmente cargada.
Cuidado con las trampas de 100vh
Los videos en pantalla completa presentan otra trampa sutil.
En el pasado, los desarrolladores solían escribir:
height: 100vh;
Los navegadores móviles complican esto porque la altura visible de la ventana cambia a medida que el borde del navegador (barra de direcciones, barras de herramientas) aparece y desaparece, por lo que 100vh no funciona como uno esperaría.
Para diseños que necesitan adaptarse bien en diferentes dispositivos, suele ser mejor utilizar unidades de ventana más modernas, como:
min-height: 100svh;
o, dependiendo de cuánta pantalla se desea que ocupe el elemento principal:
min-height: 80svh;
En resumen: prefiera 100dvh o 100svh en lugar del antiguo 100vh.
Evite servir videos del tamaño de escritorio a usuarios móviles
Incluso una página con un CLS perfecto puede seguir pareciendo extremadamente lenta si el video de fondo es muy grande.
Aquí es donde el trabajo relacionado con el rendimiento se vuelve más delicado.
Un clip cinematográfico y detallado de 12 MB puede reproducirse perfectamente con una conexión de escritorio rápida, pero en un dispositivo móvil a través de una red inestable, ese mismo archivo se convierte en un verdadero problema.
Para la mayoría de las páginas de destino, tiene sentido proporcionar recursos de video distintos para pantallas de escritorio y móviles.
En algunos casos, lo más adecuado es omitir por completo el video para ciertos visitantes.
Respetar la preferencia del usuario por reducir el movimiento es un buen ejemplo de esto:
const prefersReducedMotion =
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
Dentro de una aplicación React real, esta verificación debe realizarse en un componente del cliente, y se debe manejar con cuidado para que no deteriore el HTML que ya fue renderizado en el servidor.
La regla subyacente es sencilla: las consideraciones de rendimiento y accesibilidad deben determinar si el video se reproduce o no, y no solo la velocidad a la que se descarga.
Nunca deje que el video dicte el diseño
Este es el principio rector que debe tenerse en cuenta en todo momento.
La estructura de su página debería funcionar correctamente con esta secuencia:
Hero container
↓
Poster
↓
Video enhancement
Y nunca dependa de esta otra:
Video starts loading
↓
Browser discovers dimensions
↓
Hero changes height
↓
Everything below moves
↓
Lighthouse gets angry
El navegador necesita haber comprendido ya su diseño antes de que aparezca el recurso multimedia más pesado.
Esa es la solución real para los problemas CLS causados por el video.
Para verificarlo en la práctica, cargue su sitio mediante una conexión de red más lenta y con limitaciones de ancho de banda, y observe cómo se comporta.
Leer más
- Diferir efectos colaterales en Next.js con la API after() — Aprenda cómo la API after() de Next.js ejecuta tareas de análisis, registro y en segundo plano después de la respuesta, además de sus garantías, riesgos y compromisos en el manejo de errores.
- Ajustar los nuevos controles de truncamiento de Turbopack en Next.js 16.3 — Un análisis práctico de la nueva configuración turbopackChunking en Next.js 16.3, explicando cómo maxChunkCountPerGroup y generateComponentChunks afectan el tamaño del paquete y el caché.