Use efficient cache lifetimes en Lighthouse: cómo solucionarlo
Lighthouse 13 lista cada archivo que el navegador de los visitantes puede reutilizar durante menos de 30 días. Lo que cuesta en la página siguiente y en la visita recurrente, y la solución para cada servidor y plataforma.
¿Empeora "Use efficient cache lifetimes" la puntuación de Lighthouse?
Lighthouse marca cada archivo estático con una duración de caché inferior a 30 días en el insight Use efficient cache lifetimes ("Una duración de caché larga puede acelerar las visitas recurrentes a tu página"), llamado "Serve static assets with an efficient cache policy" antes de Lighthouse 13 y "Leverage browser caching" en versiones anteriores de PageSpeed Insights. No cuenta para la puntuación ni puede alterarla: Lighthouse siempre carga con la caché vacía. Configura duraciones más largas en el servidor, CDN o plugin de caché.
Table of Contents!
¿Qué significa Use efficient cache lifetimes en Lighthouse?
La descripción de Lighthouse dice: "Una duración de caché larga puede acelerar las visitas recurrentes a tu página. Más información sobre la caché".
Lighthouse lista todos los scripts, hojas de estilo, fuentes, imágenes, vídeos y archivos de audio con una duración de caché inferior a 30 días, o sin ninguna duración. La duración es el max-age en la cabecera Cache-Control del archivo o el tiempo hasta su fecha Expires. Los archivos de otros dominios se muestran igual que los propios.
Lighthouse omite cualquier archivo cuyo Cache-Control indique no-cache, no-store, private o must-revalidate, sea cual sea su max-age: asume que es una decisión deliberada de no cachear. También descarta los archivos con max-age=0 y nunca incluye la propia página HTML.

Una página que carga Google Tag Manager, GA4 y el Meta Pixel. Google envía sus etiquetas como private, por lo que de las tres solo aparece el Meta Pixel. Lighthouse 13.5.0, throttling simulado como en PageSpeed Insights.
¿Cómo funciona la caché del navegador?
El navegador guarda el archivo junto con su duración. En la página siguiente o en la próxima visita, consulta primero la copia guardada:
- Dentro de su duración: el navegador usa su copia y no envía ninguna petición. Es el único caso sin esperas al servidor.
- Duración vencida: el navegador pregunta al servidor si el archivo ha cambiado, mediante el ETag o la fecha Last-Modified que recibió con el archivo. Si no ha cambiado, el servidor responde 304 Not Modified, sin el archivo. Eso ahorra la descarga, pero cada archivo sigue costando un round trip al servidor, y la página no puede pintar hasta que el servidor haya respondido para cada hoja de estilo.
- Duración vencida y sin ETag ni Last-Modified: el navegador descarga el archivo de nuevo.
| Cabecera del archivo | Siguiente visita en Chrome |
|---|---|
max-age=31536000 | Desde la caché durante un año |
max-age=14400 | Desde la caché durante 4 horas; luego un 304 |
no-cache | Un 304 cada vez |
no-store | Se descarga cada vez |
Una recarga normal en Chrome no vuelve a comprobar los archivos que aún están dentro de su duración. Firefox y Safari sí lo hacen, a menos que el archivo incluya immutable en su Cache-Control.
Desde Chrome 86, cada sitio tiene su propia caché. Si un visitante ya descargó una fuente o un archivo de jQuery en otro sitio, volverá a descargarlo en el tuyo, aunque sea desde la misma URL. De modo que la duración de un tercero solo sirve dentro de tu propio sitio, en la página siguiente y en la visita recurrente, y decir que "todo el mundo ya lo tiene en caché" ya no es motivo para cargar fuentes de Google (alójalas tú mismo).
La caché del navegador y la caché de la CDN son dos cosas distintas, al igual que la caché de página de un plugin de WordPress. Una CDN o una caché de página guarda una copia para que el servidor no tenga que enviarla ni generarla para cada visitante, lo que reduce el TTFB (duración de la caché). La caché del navegador ahorra la petición completa para un único visitante. Este insight solo evalúa la caché del navegador, así que tener activada una caché de página no lo resuelve.
¿Una mayor duración de caché mejora el LCP y el FCP?
FCP y LCP en una primera visita
No cambia nada. En una página de producto de WooCommerce, en un móvil con Slow 4G, el primer pintado fue a los 2,13 s y el LCP llegó a los 2,26 s con cualquier cabecera, desde ninguna hasta un año: en una primera visita todavía no hay nada en la caché. Lighthouse y PageSpeed Insights siempre cargan las páginas de esta manera. Corregir este insight nunca modifica la puntuación ni las métricas de Lighthouse, incluso cuando Lighthouse lo muestra en rojo con un ahorro estimado de FCP y LCP al lado.

Sin duración

Las 4 horas de Cloudflare

Un año

no-store
La misma página de producto con cuatro cabeceras de caché distintas en sus archivos. Lighthouse 13.5.0, móvil, throttling simulado.
FCP y LCP en la siguiente página y en una visita recurrente
El primer pintado (FCP) en segundos, frente a los 2,13 s de la primera visita:
| Cabecera de caché | Siguiente página | Una semana después |
|---|---|---|
| un año | 0,79 | 0,78 |
| 4 horas | 0,80 | 1,94 |
no-cache | 1,93 | 1,94 |
no-store | 2,11 | 2,12 |
- La mejora viene de los archivos dentro de su duración. Una semana después, la página con un año de duración pintó a los 0,78 s, aproximadamente un tercio de la primera visita. Lo único que queda es la espera del HTML. En la visita recurrente, el LCP (la foto del producto) ocurrió en el mismo instante que el primer pintado con todas las cabeceras, excepto con no-store (2,24 s).
- Con respuestas 304, la visita recurrente fue casi tan lenta como la primera. Sin duración de caché, una semana después los 13 archivos propios de la tienda respondieron con 304: 1 kB transferido por la red en lugar de 404 kB, y aun así el primer pintado tardó 1,94 s, apenas 0,19 s más rápido que volver a descargarlo todo.
- Las duraciones cortas cubren la visita pero caducan antes del regreso. Las 4 horas de Cloudflare sirvieron la página siguiente desde la caché. Una semana después, hicieron falta respuestas 304. Solo la opción de un año cubrió ambos casos.
- no-cache y no-store ralentizan cada página. Hicieron que la página siguiente pasara de 0,79 s a 1,93 s y 2,11 s.

Sin duración, una semana después

Un año de duración, una semana después
Ambas páginas a los 0,8 s en el móvil con throttling.
En una conexión Slow 4G real, cada carga tarda más; el orden relativo entre las cabeceras se mantiene. En Fast 4G, con un año de duración el primer pintado fue a los 0,52 s en la visita recurrente, mientras que con 304 tardó 0,85 s: lo mismo que la primera visita en esa conexión.
¿Cuándo puedes ignorar Use efficient cache lifetimes?
Si Lighthouse lista archivos propios y los visitantes regresan días o semanas después, corrígelo: basta con una regla en el servidor y no cuesta nada durante la carga de la página. Puedes ignorarlo cuando:
- todos los archivos de la lista están en otro dominio o los sirve tu plataforma (ver más abajo); en Shopify siempre es así;
- el CSS y el JavaScript cambian con el mismo nombre de archivo y no puedes añadir una versión: en ese caso, una duración corta es la decisión correcta, y Lighthouse seguirá listando esos archivos;
- casi todos los visitantes ven una sola página y no vuelven nunca, como en una landing page para una campaña publicitaria: nadie aprovecha la caché.
¿Cómo mides el efecto de la duración de caché en tus field data?
Tus field data mezclan primeras visitas con páginas siguientes y visitas recurrentes. En CoreDash, agrupa el LCP en móvil por Repeat Visitor, y luego el TTFB de la misma forma:


CoreDash en un sitio de prueba, acme-pagespeed.com: móvil, p75.
Los visitantes recurrentes tienen un LCP de 0,65 s; los nuevos visitantes, de 1,74 s. No toda esa diferencia se debe a la caché: los visitantes recurrentes también reciben el primer byte antes (323 ms frente a 0,87 s), a menudo porque su navegador ya tiene abierta una conexión con el servidor. Resta el TTFB al LCP: tras el primer byte, los visitantes recurrentes esperan 0,33 s y los nuevos, 0,87 s. Esos 0,54 s son aproximadamente lo que ahorra la caché, cerca de la mitad de la diferencia; el primer byte más rápido explica la otra mitad.
Para aislar la siguiente página de una visita, agrupa por Navigation Origin: same origin indica que el visitante vino de otra página de tu sitio. CrUX agrupa todas estas visitas en un único p75 por página o por sitio, por lo que no puede mostrar este desglose.
¿Cómo compruebas las cabeceras de caché de un archivo en DevTools?
Abre el panel Network y asegúrate de que Disable cache esté desactivado (la configuración de red de DevTools explica cuándo activarlo). Carga la página y navega a una segunda página. Las columnas Status y Size muestran qué ocurrió con cada archivo:
- (disk cache) o (memory cache) en Size: servido desde la caché, sin petición.
- 304 en Status: el navegador consultó al servidor y no recibió ningún archivo.
- 200 con un tamaño: descargado de nuevo.

Sin duración: 304 para cada archivo

Un año: desde la disk cache
La visita recurrente una semana después en el panel Network, Slow 4G.
Haz clic en un archivo y abre la pestaña Headers. En Response Headers, cache-control y expires muestran la duración enviada por el servidor; etag y last-modified, los valores con los que el navegador puede revalidar. Desde la línea de comandos:
curl -sI https://www.example.com/wp-content/themes/acme/style.css | grep -iE "cache-control|expires|etag|last-modified" El panel Performance incluye el mismo insight. Graba una recarga y abre Use efficient cache lifetimes en la barra lateral de Insights: listará los archivos y los destacará en el track Network. Un archivo sin duración aparece allí como 0 ms, y como None en Lighthouse. Se basa en las cabeceras, por lo que un archivo servido desde la caché seguirá apareciendo si su duración es corta.

¿Cómo solucionas Use efficient cache lifetimes?
El dominio de cada archivo que lista Lighthouse indica quién define la cabecera: en tu propio dominio es tu servidor, tu hosting o tu CDN; en cualquier otro dominio es el proveedor de ese archivo.
nginx, Apache, IIS y Caddy
En un servidor propio, basta una regla por tipo de archivo para definir la duración. Estas reglas asignan un año, con immutable: úsalas para imágenes y para CSS, JavaScript y fuentes en cuanto sus URL incluyan una versión (qué duración asignar a cada archivo). En nginx:
location ~* \.(?:css|js|mjs|woff2|svg|png|jpe?g|gif|webp|avif|ico)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
} Añádela al bloque server que sirve estos archivos desde disco. Si la configuración ya tiene un bloque location para estas extensiones, añade la línea add_header en ese bloque, ya que nginx solo utiliza la primera location con expresión regular que coincide. Elimina cualquier directiva expires que contenga, o los archivos recibirán dos cabeceras Cache-Control. Un add_header dentro de un location hace que nginx descarte todos los add_header del bloque server para esos archivos; copia también en este location las cabeceras add_header del bloque server (por ejemplo, la de HSTS).
En Apache, añade esto en el .htaccess:
<IfModule mod_headers.c>
<FilesMatch "\.(css|js|mjs|woff2|svg|png|jpe?g|gif|webp|avif|ico)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule> FilesMatch se basa en el nombre del archivo, por lo que las páginas generadas por PHP mantienen sus propias cabeceras. Si después de esto Lighthouse sigue listando los archivos, el servidor no está aplicando la regla: mod_headers está desactivado o el hosting sirve estos archivos desde otro servidor que ignora el .htaccess. Consúltalo con tu hosting.
Por defecto, IIS no envía ninguna duración. Define la cabecera en web.config, delimitada a la carpeta de los archivos estáticos:
<configuration>
<location path="assets">
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Cache-Control" value="public, max-age=31536000, immutable" />
</customHeaders>
</httpProtocol>
</system.webServer>
</location>
</configuration> En Caddy:
@static path *.css *.js *.mjs *.woff2 *.svg *.png *.jpg *.jpeg *.gif *.webp *.avif *.ico
header @static Cache-Control "public, max-age=31536000, immutable" WordPress
WordPress no envía ninguna duración por sí mismo para nada dentro de /wp-content/ ni /wp-includes/: lo decide el hosting, el servidor o un plugin. Kinsta, WP Engine, WordPress VIP y WordPress.com ya envían un año; si estás alojado allí, los archivos que lista Lighthouse suelen pertenecer a otros dominios (ver más abajo). El aviso suele aparecer en hostings compartidos con Apache que no envían duración, en servidores autogestionados con nginx y tras la configuración por defecto de Cloudflare. En los plugins de caché:
- WP Rocket escribe reglas mod_expires en el .htaccess de servidores Apache y LiteSpeed: un año para CSS y JavaScript, cuatro meses para imágenes y fuentes. En nginx no hace nada: añade la regla de nginx anterior o pídeselo a tu hosting.
- LiteSpeed Cache viene con Browser Cache desactivado por defecto. Actívalo en LiteSpeed Cache, Cache, Browser; pasará a enviar un año.
- W3 Total Cache viene con Browser Cache activado por defecto. En nginx genera un archivo nginx.conf que debes incluir manualmente en la configuración del servidor.
WordPress añade ?ver= a cada hoja de estilo y script que carga: la versión de WordPress por defecto, o la que pasa el tema o el plugin. Eso solo cambia con una actualización. Si editas tu propio CSS sin cambiar la versión, la URL no varía y los visitantes recurrentes se quedan con el CSS antiguo mientras dure su caché. Carga los archivos propios usando su fecha de modificación como versión, para que la URL cambie con cada edición:
add_action( 'wp_enqueue_scripts', function () {
$file = '/css/custom.css';
wp_enqueue_style( 'acme-custom', get_stylesheet_directory_uri() . $file, array(), filemtime( get_stylesheet_directory() . $file ) );
} ); Las subidas de medios no llevan versión. Cuando vuelves a subir un archivo, WordPress le asigna un nuevo nombre (photo-1.jpg), así que fijar un año es seguro, a menos que algún plugin reemplace los archivos multimedia manteniendo el mismo nombre.
Cloudflare y otras CDN
El TTL de Browser Cache en Cloudflare es de 4 horas por defecto en todos los planes. Si el servidor no envía duración o envía una más corta, Cloudflare envía 4 horas y Lighthouse marca el archivo con "4h". Configura las duraciones en el servidor y cambia Browser Cache TTL (Caching, Configuration) a Respect Existing Headers. O crea una Cache Rule para los tipos de archivo estáticos que defina el TTL del navegador. No subas el ajuste global de la zona a un año: se aplicará a todos los archivos que Cloudflare sirva con una cabecera más corta, incluido el CSS sin versionar. Más detalles sobre el resto de ajustes en cómo configurar Cloudflare para pasar las Core Web Vitals.
CloudFront y Fastly reenvían al navegador la cabecera del servidor; sus ajustes de TTL solo modifican el tiempo que la CDN conserva la copia. En Bunny, una Edge Rule con Override Browser Cache Time permite definir la duración por extensión de archivo.
Shopify
Shopify ya asigna un año a los archivos del tema, archivos subidos e imágenes de producto (public, max-age=31557600), y no permite modificar sus cabeceras. Lo que Lighthouse lista en Shopify proviene de la propia plataforma y de tus aplicaciones: los botones de pago dinámico (portable-wallets, 5 minutos, en tu propio dominio bajo /cdn/shopifycloud/), el loader de shop-js (30 minutos) y el gestor de web pixels (60 segundos). No hay nada que corregir. Ignora el aviso.
Next.js y otras herramientas de build
Next.js asigna un año con immutable a todo lo que está bajo /_next/static/, y no se puede sobrescribir. Los archivos en public/ reciben max-age=0 (en Vercel con must-revalidate) y /_next/image envía must-revalidate, por lo que el insight omite ambos. En un sitio con Next.js, los archivos que lista Lighthouse proceden de terceros, o bien de un proxy o CDN intermedio que reescribe las cabeceras. Para los archivos de public/ cuyo nombre cambia según su contenido, define un año en next.config.js:
module.exports = {
async headers() {
return [{
source: '/fonts/:path*',
headers: [{ key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }],
}]
},
} En cualquier otro stack, haz que el bundler incluya un hash en el nombre de los archivos (app.3f9a1c.js), de modo que cada cambio reciba una nueva URL. Luego, envía un año en esos archivos.
Archivos de terceros y de plataforma que no puedes modificar
Los proveedores mantienen corta la duración de sus scripts para poder actualizarlos en todos los sitios a la vez, y no puedes cambiar sus cabeceras. Lighthouse suele listar estos ejemplos, con la duración que envía cada proveedor:
- Meta Pixel (fbevents.js): 20 minutos
- Hotjar: 60 segundos
- El antiguo analytics.js de Google Analytics: 2 horas
- Google Maps (maps/api/js): 30 minutos
- Stripe (js.stripe.com/v3): 2 minutos
- Scripts propios de Cloudflare en tu dominio (email-decode.min.js, Rocket Loader): 2 días
Déjalos estar. No alojes tú mismo un loader como fbevents.js o gtag.js para meterle tu propia cabecera: tu copia dejará de recibir las actualizaciones del proveedor. Lo que sí puedes hacer:
- Carga una librería desde una CDN pública con una versión exacta en la URL. jsDelivr asigna siete días a swiper@11 y un año a una versión exacta. O aloja tú mismo esa versión fijada bajo tu propia cabecera.
- Desactiva las funciones de Cloudflare que no necesites, como Email Address Obfuscation.
- Elimina etiquetas que ya nadie use.
Qué duración de caché asignar a cada archivo
- CSS, JavaScript y fuentes con versión o hash en la URL: un año, con immutable:
Cache-Control: public, max-age=31536000, immutable. Una nueva versión supone una nueva URL, así que el navegador la descarga de inmediato. - Imágenes: la misma cabecera. Si cambias una imagen, dale un nuevo nombre de archivo y nadie volverá a ver la versión antigua.
- CSS, JavaScript y fuentes sin versión: añade una versión primero (los pasos de WordPress y herramientas de build descritos arriba). Hasta que lo hagas, mantén una duración corta y acepta que Lighthouse los liste: si pones una duración larga a una hoja de estilo que cambia bajo el mismo nombre, los visitantes recurrentes recibirán el HTML nuevo con el CSS viejo.
- El HTML:
Cache-Control: no-cacheo un max-age corto, porque con una duración larga los visitantes se quedarían con una página desactualizada. - Nunca uses no-store, no-cache o must-revalidate en archivos estáticos para quitar el aviso, porque cada carga de página volverá a revalidar o a descargar todos los archivos (ver la siguiente página más arriba).
¿Cómo compruebas que las nuevas duraciones de caché funcionan?
Comprueba primero la cabecera en el archivo, con curl o en la pestaña Headers (como vimos arriba): debería mostrar el nuevo max-age. Luego ejecuta Lighthouse en móvil. Ya no listará los archivos, y la puntuación y las métricas se mantendrán donde estaban, tal como en los cuatro informes de arriba. En la página de producto, la puntuación osciló entre 96 y 80 entre ejecuciones, y esa variación no tuvo nada que ver con la cabecera; por tanto, una puntuación distinta tras el cambio no te dice nada sobre la caché.
Los visitantes recurrentes en CoreDash lo reflejan primero, en cuestión de días, a medida que regresan y reciben los archivos con su nueva duración. CrUX y Search Console muestran los últimos 28 días, por lo que dos semanas después del cambio la mitad de sus datos aún corresponde al sitio antiguo.
¿No estás seguro de cuáles de los archivos listados te corresponde corregir, o de si tus visitantes recurrentes esperan por archivos que ya tienen? En una power hour de Core Web Vitals revisamos juntos tus cabeceras y tus field data, y te llevas las reglas para tu servidor antes de colgar.
Pongo webs en verde de Core Web Vitals.
Más de 500K páginas para editores y e-commerce grandes en Europa. Yo escribo los fixes y los verifico con datos reales.
Así trabajo