Ingeniería
Un sitio de estudio en menos de 1 s: nuestro presupuesto de rendimiento
Cómo lanzamos sitios de estudio que muestran el primer contenido en menos de un segundo en un teléfono de gama media con 4G: el presupuesto que nos exigimos, lo que recortamos y las pequeñas decisiones de arquitectura que más pesan en las Core Web Vitals.
En este artículo
Hay un tipo concreto de sitio de estudio (ese en el que cada scroll dispara un destello de parallax, un cambio de fuente, un reel en reproducción automática que se queda cargando tres segundos y un cursor al que le salen colmillos al pasar por encima) que tarda ocho segundos en cargar en un teléfono de gama media y parece roto en cualquier conexión que no sea fibra. Nosotros no hacemos sitios así. El brief que nos imponemos es brutal y simple: First Contentful Paint por debajo de un segundo, en un Pixel 6a, con una conexión 4G limitada.
Cumplir ese brief no es cuestión de optimizaciones heroicas. Es cuestión de unas pocas decisiones de arquitectura que se suman entre sí y de muchas más cosas que no hacemos. Este artículo recoge el presupuesto con el que trabajamos, las decisiones que lo sustentan y las mediciones de campo que demuestran que el presupuesto se cumplió. Si vas a lanzar el sitio de un estudio, de una agencia o cualquier superficie con forma de portafolio, este es un objetivo real que puedes copiar casi tal cual.
El presupuesto, en cifras
Cada sitio de estudio que entregamos apunta a estas cifras en un Pixel 6a con la limitación Fast 3G de Chrome DevTools. Con una conexión 4G real va más rápido; usamos Fast 3G como límite inferior para el peor caso. Medimos con Lighthouse y con métricas de usuarios reales en producción mediante Vercel Analytics.
- Largest Contentful Paint (LCP): menos de 1,2 s en laboratorio y menos de 1,8 s en el p75 de campo. El umbral «bueno» de Google es 2,5 s.
- First Contentful Paint (FCP): menos de 1,0 s en laboratorio.
- Cumulative Layout Shift (CLS): menos de 0,05. El umbral «bueno» de Google es 0,1; nosotros nos exigimos la mitad.
- Interaction to Next Paint (INP): menos de 100 ms. El umbral «bueno» de Google es 200 ms.
- Tamaño total de transferencia en la primera carga: menos de 150 KB comprimidos, incluidos HTML, CSS, fuentes, JS y la imagen hero.
- Bundle de JavaScript en la primera carga: menos de 80 KB comprimidos.
Si una página falla en cualquiera de estas cifras, la tratamos como un bug P1. No como «algo a lo que volver más adelante». Un P1 que corregimos antes de que el sitio salga.
¿Por qué tan estrictos?
Por dos razones. Primero, las Core Web Vitals son un factor de posicionamiento para Google. No un criterio de desempate: un factor de posicionamiento. Los sitios que cumplen los umbrales pueden optar al impulso por experiencia de página; los que no, salen penalizados en el margen. Para un estudio que compite por grupos de palabras clave muy concretos, ese margen importa.
Segundo, el rendimiento percibido es una señal de marca. Un sitio que carga al instante transmite competencia. Un sitio que se queda colgado mientras se resuelve un píxel de seguimiento transmite lo contrario. Para un estudio cuya propuesta entera es «hacemos trabajo moderno y bien pensado», el sitio tiene que demostrar esa propuesta en el primer contacto. El sitio es el brief.
Decisión 1: Renderizar todo de forma estática
La decisión de rendimiento más importante es la estrategia de renderizado. Usamos Next.js con el App Router, y cada página de nuestros sitios de estudio se renderiza de forma estática en tiempo de build. Sin consultas a base de datos en tiempo de ejecución. Sin trabajo de servidor por petición más allá de la función edge que sirve el HTML. Todo el sitio son archivos en una CDN.
Lo que te da esto, en concreto: el TTFB en el edge es el que diga tu CDN, normalmente 30–80 ms en cualquier parte del planeta. El primer byte de HTML llega antes de que el navegador haya terminado el handshake TLS con una alternativa renderizada en el servidor. Todo lo que viene después (el pintado, la interactividad, la página siguiente) empieza con esa misma ventaja.
La contrapartida es que los cambios de contenido requieren un despliegue. Para el sitio de un estudio que se actualiza cada mes, desplegar no tiene ninguna complicación: lleva 90 segundos en Vercel. Para una publicación que se actualiza cada hora, las cuentas son otras e ISR (Incremental Static Regeneration) es la herramienta adecuada. Pero los sitios de estudio no se actualizan cada hora.
Decisión 2: Una fuente web, dos pesos
Las fuentes personalizadas son el mayor gasto controlable del primer pintado. Un subconjunto de fuente web para un solo peso ocupa normalmente 25–40 KB comprimido. Un sitio que carga tres pesos de dos familias envía 150 KB de fuentes antes de que se pinte el primer byte de contenido. Nosotros enviamos una familia en dos pesos, servida con la entrega autoalojada y con hash de next/font: sin petición de terceros a Google Fonts ni búsqueda DNS a una CDN de fuentes.
También aceptamos la consecuencia: no hay cursiva, ni thin, ni extrabold. Cada distinción tipográfica tiene que resolverse con tamaño, color, espaciado entre letras o el contraste entre los dos pesos que tenemos. Es una restricción creativa que se paga sola. Los sitios que necesitan ocho pesos suelen esconder una jerarquía pobre tras la variedad tipográfica; los que funcionan con dos pesos tienen que ganarse la jerarquía con la estructura.
Decisión 3: El presupuesto de la imagen hero, y cómo gastarlo
En el sitio de un estudio, el LCP es casi siempre el hero. Así que el presupuesto de LCP es el presupuesto del hero. Le damos un techo estricto: 80 KB comprimidos en el breakpoint de escritorio y 40 KB en móvil. Cualquier cosa mayor es un reto creativo que resolver en la selección o el tratamiento de la imagen, no una excusa para pasarse del presupuesto.
Cuatro prácticas que nos mantienen dentro del presupuesto:
- Usar AVIF donde haya soporte, con WebP como alternativa. AVIF suele ser un 30–40 % más ligero que WebP a igual calidad.
- Servir tamaños responsive con el componente next/image. Los teléfonos nunca descargan el hero de escritorio; los equipos de escritorio nunca descargan la versión retina 2x a menos que la pantalla sea realmente retina.
- Definir width y height explícitos en cada imagen. Esto es lo que evita los cambios de diseño: el navegador reserva el número correcto de píxeles antes de que llegue la imagen.
- Precargar la imagen hero. <link rel="preload" as="image" href="…"/> en el head. Cuesta una línea y ahorra 200–400 ms de LCP, porque el navegador empieza a pedir la imagen antes de analizar el resto de la página.
Decisión 4: JavaScript como último recurso
Cada kilobyte de JavaScript es un kilobyte que hay que descargar, analizar y ejecutar en el hilo principal antes de que la página sea interactiva. Tratamos el presupuesto de JS como un bien escaso y seguimos dos reglas:
- Server components por defecto. Marca un componente como 'use client' solo si de verdad necesita interactividad. El comportamiento por defecto de React Server Components es el adecuado para sitios con mucho contenido.
- Ningún script de terceros en la primera carga. Nada de analítica que envíe 80 KB de código del proveedor. Nada de gestores de etiquetas. La analítica se carga en diferido cuando la página ya es interactiva: usamos Vercel Analytics, que ocupa alrededor de 1 KB.
El presupuesto que exigimos a nuestros sitios de estudio es de menos de 80 KB de JS comprimido en la primera carga, la mayor parte del cual es el propio React. La capa interactiva (menú de navegación, selector de idioma, formulario de contacto) ocupa unos pocos kilobytes.
Decisión 5: Una arquitectura CSS sin bugs de cambios de diseño
Cumulative Layout Shift es la Core Web Vital más fácil de suspender por accidente y la más fácil de arreglar a propósito. Los cuatro hábitos que mantienen el CLS cerca de cero:
- Reserva espacio para todo lo que llega tarde. Imágenes, fuentes, iframes, anuncios: todo tiene un contenedor con aspect-ratio y dimensiones explícitas.
- Evita el CSS que depende de que la fuente esté cargada. Usa fuentes del sistema como alternativa durante la ventana de cambio, con font-display: swap. Elige una alternativa cuyas métricas coincidan con las de la fuente web para que el cambio sea invisible.
- No inyectes contenido por encima del pliegue después de la carga. Los banners, avisos de cookies y barras informativas que aparecen tras el primer pintado son un cambio de diseño a punto de ocurrir. Si los necesitas, renderízalos en el HTML inicial y haz que entren con una animación.
- Prueba en redes lentas. Los problemas de CLS que no aparecen con fibra aparecen en 3G, porque el recurso que llega tarde llega todavía más tarde.
Decisión 6: El presupuesto de animación
La contención en el movimiento es una optimización de rendimiento en sí misma. Cada animación que se ejecuta en el hilo principal pone en riesgo el INP: una sola animación de 250 ms que va a trompicones al hacer clic saca a todo el sitio de la franja «buena» de INP. Nuestras reglas:
- Anima solo transform y opacity. Ambas se componen en la GPU y no provocan recálculos de layout.
- Las animaciones de aparición son CSS, no JavaScript. Usamos un IntersectionObserver para añadir un className; la animación en sí es una transición.
- Nada de animaciones vinculadas al scroll. Suenan elegantes y dan un rendimiento de desplazamiento pésimo en dispositivos de gama media.
- Nada de video en reproducción automática en el hero. Si hace falta video, cárgalo en diferido por debajo del pliegue o tras una interacción.
Decisión 7: Donde se cruzan accesibilidad y rendimiento
La mayoría de las mejoras de accesibilidad también son mejoras de rendimiento. El HTML semántico pesa menos que una sopa de divs. Los anillos de foco nativos se renderizan más rápido que una gestión de foco personalizada con JS. Las cabeceras con enlace para saltar al contenido hacen innecesarias las correcciones de trampas de teclado que añaden JavaScript. Diseñamos y construimos pensando primero en la accesibilidad; el rendimiento llega gratis.
El único punto en el que rendimiento y accesibilidad entran en tensión es la animación. Algunos usuarios necesitan movimiento; otros se marean. Respetamos prefers-reduced-motion y acompañamos cada animación de aparición de una alternativa que es un fundido, no un desplazamiento.
Medir en producción
Las métricas de laboratorio de Lighthouse son necesarias, pero no suficientes. La referencia de oro es el seguimiento de usuarios reales (RUM): lo que viven los visitantes reales en dispositivos reales. Usamos Vercel Analytics para las Web Vitals porque viene gratis con la plataforma e informa de métricas de campo segmentadas por ruta, dispositivo y geografía.
Lo que revisamos cada semana:
- LCP p75 por ruta. Si el p75 de cualquier ruta supera 1,8 s, investigamos.
- Regresiones de INP. El INP es la métrica más traicionera: puede degradarse en silencio a medida que crece el JavaScript.
- CLS por ruta. Las regresiones de CLS suelen deberse a un único elemento que carga tarde.
- JS de primera carga por ruta, controlado con el analizador de bundles en CI. Hacemos fallar los builds que superan el presupuesto.
Lo que no hacemos
Igual de importante: lo que dejamos fuera a propósito, aunque sea estándar en el sector:
- Nada de enrutamiento en el cliente al estilo SPA en un sitio pequeño. En una conexión 4G, pedir el documento completo es más rápido que el delta del bundle. Dejamos que navegue el navegador.
- Nada de service worker. En un sitio de menos de 10 páginas, la complejidad no compensa.
- Nada de servicios de alojamiento de imágenes que inyectan un SDK de 30 KB. Servimos las imágenes desde el mismo dominio que el sitio, optimizadas en tiempo de build.
- Nada de bibliotecas de lazy-load «inteligentes». El atributo nativo loading="lazy" hace el trabajo con 0 KB.
- Nada de precargar cada enlace al pasar el cursor. Next.js hace prefetch por defecto en producción; lo dejamos activado y dejamos que decida el framework.
Si estás definiendo el alcance de un sitio y quieres una auditoría de rendimiento de lo que ya tienes, o quieres uno construido con este presupuesto desde el principio, escríbenos a hello@neptay.com.