Documentación de UXR SEO Analyzer

Introducción

Headers de Caché HTTP

Ver contenido

Introducción

El caché HTTP permite a los navegadores almacenar copias de recursos localmente, eliminando la necesidad de descargarlos nuevamente en visitas posteriores. Un caché adecuado puede reducir los tiempos de carga de página en un 50-90% para visitantes recurrentes y disminuir significativamente la carga del servidor y costos de ancho de banda.

Entender cómo funcionan los headers de caché es esencial para entregar experiencias web rápidas y eficientes mientras se asegura que los usuarios siempre vean contenido actualizado cuando sea necesario.

Cómo Funciona el Caché del Navegador

Cuando un navegador solicita un recurso, los headers de respuesta del servidor le dicen al navegador si y cómo cachear ese recurso:

Flujo de Caché HTTP:
┌─────────────────────────────────────────────────────────────┐
│ Primera Visita:                                             │
│ 1. Navegador solicita: GET /styles.css                      │
│ 2. Servidor responde con recurso + headers de caché:        │
│    Cache-Control: max-age=31536000                          │
│    ETag: "abc123"                                           │
│ 3. Navegador almacena recurso en caché local                │
│                                                             │
│ Visitas Posteriores (dentro de max-age):                    │
│ 1. Navegador verifica caché local                           │
│ 2. Caché es fresco → Usa copia cacheada (¡sin red!)         │
│ 3. Página carga instantáneamente desde caché                │
│                                                             │
│ Después de que max-age expira:                              │
│ 1. Navegador envía solicitud condicional con ETag           │
│ 2. Servidor retorna 304 Not Modified (si no cambió)         │
│ 3. Navegador usa copia cacheada (ancho de banda mínimo)     │
└─────────────────────────────────────────────────────────────┘

Headers de Caché Principales

Cache-Control

El header principal para controlar el comportamiento del caché:

Directiva Significado Ejemplo de Uso
max-age=N Cachear por N segundos Assets estáticos
no-cache Debe revalidar antes de usar Páginas HTML
no-store Nunca cachear Datos sensibles
public Puede ser cacheado por CDNs Recursos estáticos
private Solo el navegador puede cachear Datos de usuario
immutable Nunca cambia (omitir revalidación) Assets versionados

ETag (Entity Tag)

Un identificador único para una versión específica de un recurso:

Respuesta del Servidor:
ETag: "33a64df5"

Solicitud de Revalidación del Navegador:
If-None-Match: "33a64df5"

Respuesta del Servidor (si no cambió):
304 Not Modified

Last-Modified

Validación basada en timestamp (más antigua que ETag pero aún útil):

Respuesta del Servidor:
Last-Modified: Wed, 15 Dec 2025 10:00:00 GMT

Solicitud de Revalidación del Navegador:
If-Modified-Since: Wed, 15 Dec 2025 10:00:00 GMT

Expires (Legacy)

Establece una fecha de expiración absoluta. Usa Cache-Control: max-age en su lugar cuando sea posible.

Estrategias de Caché por Tipo de Recurso

Diferentes recursos necesitan diferentes estrategias de caché:

Tipo de Recurso Política de Caché Recomendada Por Qué
HTML no-cache o max-age corto El contenido cambia frecuentemente
CSS/JS (versionados) max-age=31536000, immutable El nombre de archivo cambia al actualizar
Imágenes max-age=31536000 Raramente cambian
Fuentes max-age=31536000 Nunca cambian
Respuestas de API no-store o max-age corto Frescura de datos crítica

Assets Versionados vs No Versionados

Versionados (Recomendado):
├── styles.abc123.css  → max-age=1 año (immutable)
├── app.def456.js      → max-age=1 año (immutable)
└── Cuando el contenido cambia, el nombre de archivo cambia

No Versionados (Problemático):
├── styles.css         → max-age=? (riesgoso)
├── app.js             → max-age=? (riesgoso)
└── Los usuarios pueden obtener contenido obsoleto

Impacto en el Rendimiento

El caché adecuado mejora dramáticamente las métricas de rendimiento:

Métrica Primera Visita Visita Cacheada Mejora
Tiempo de Carga 3.2s 0.8s 75% más rápido
Datos Transferidos 2.1 MB 45 KB 98% menos
Solicitudes al Servidor 45 5 89% menos
TTFB 450ms 0ms* 100% eliminado

*Los recursos cacheados tienen TTFB cero ya que no se hace solicitud de red.

Problemas Comunes de Caché

Problema 1: Sin Headers de Caché

Problema: El servidor no envía headers de caché; el navegador usa heurísticas.

Solución: Siempre establecer explícitamente headers Cache-Control.

Problema 2: max-age Muy Corto

Problema: Assets estáticos cacheados solo por horas o días.

Solución: Usar 1 año (31536000 segundos) para assets estáticos versionados.

Problema 3: Cachear HTML

Problema: Páginas HTML cacheadas muy agresivamente; usuarios ven contenido obsoleto.

Solución: Usar no-cache para HTML para siempre revalidar.

Problema 4: Sin ETag/Last-Modified

Problema: El navegador no puede revalidar; debe re-descargar recursos expirados.

Solución: Configurar servidor para enviar ETags para revalidación eficiente.

Verificando Tus Headers de Caché

Usando DevTools del Navegador

  1. Abrir DevTools → pestaña Network
  2. Hacer clic en cualquier recurso
  3. Verificar Response Headers buscando:
    • Cache-Control
    • ETag o Last-Modified
    • Expires

Usando curl

curl -I https://ejemplo.com/styles.css

# Buscar:
# Cache-Control: max-age=31536000
# ETag: "abc123"

Usando Lighthouse

Ejecutar una auditoría de Lighthouse y verificar:

  • “Serve static assets with an efficient cache policy”
  • Muestra recursos con TTLs de caché cortos o faltantes

Implementación

Estrategia 1: Directivas Cache-Control en Profundidad

Referencia Completa de Directivas

Directivas Cache-Control:
┌─────────────────────────────────────────────────────────────┐
│ DIRECTIVAS DE FRESCURA                                      │
│ ├── max-age=<segundos>     Cuánto tiempo el caché es fresco │
│ ├── s-maxage=<segundos>    Duración de caché CDN/proxy      │
│ └── stale-while-revalidate=<segundos>  Servir obsoleto      │
│                            mientras obtiene copia fresca    │
│                                                             │
│ DIRECTIVAS DE REVALIDACIÓN                                  │
│ ├── no-cache               Siempre revalidar antes de usar  │
│ ├── must-revalidate        Revalidar después de max-age     │
│ └── proxy-revalidate       Proxies deben revalidar          │
│                                                             │
│ DIRECTIVAS DE ALMACENAMIENTO                                │
│ ├── no-store               Nunca cachear (datos sensibles)  │
│ ├── private                Solo el navegador puede cachear  │
│ └── public                 CDNs y proxies pueden cachear    │
│                                                             │
│ DIRECTIVAS DE OPTIMIZACIÓN                                  │
│ └── immutable              Nunca revalidar (assets          │
│                            versionados)                     │
└─────────────────────────────────────────────────────────────┘

Combinando Directivas

# Assets estáticos versionados (CSS, JS con hash en nombre)
Cache-Control: public, max-age=31536000, immutable

# Páginas HTML (siempre verificar actualizaciones)
Cache-Control: no-cache

# Datos sensibles de usuario (nunca cachear)
Cache-Control: no-store, private

# Respuestas de API (caché corto con refresh en background)
Cache-Control: public, max-age=60, stale-while-revalidate=600

# Caché más largo específico para CDN
Cache-Control: public, max-age=3600, s-maxage=86400

Estrategia 2: Configuración de Servidor

Configuración de Nginx

# /etc/nginx/conf.d/caching.conf

# Assets estáticos versionados - cachear para siempre
location ~* \.(css|js)$ {
    # Solo si el nombre contiene hash (ej. app.abc123.js)
    if ($uri ~* ".*\.[a-f0-9]{8,}\.") {
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
    # Respaldo para no versionados
    if ($uri !~* ".*\.[a-f0-9]{8,}\.") {
        add_header Cache-Control "public, max-age=86400";
    }
    add_header Vary "Accept-Encoding";
}

# Imágenes y fuentes - caché largo
location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico|woff|woff2|ttf|eot)$ {
    add_header Cache-Control "public, max-age=31536000";
    add_header Vary "Accept-Encoding";
}

# HTML - siempre revalidar
location ~* \.html$ {
    add_header Cache-Control "no-cache";
    add_header Vary "Accept-Encoding";
}

# Endpoints de API - caché corto con stale-while-revalidate
location /api/ {
    add_header Cache-Control "public, max-age=60, stale-while-revalidate=600";
    add_header Vary "Accept-Encoding, Authorization";
}

# Habilitar ETag
etag on;

Configuración de Apache

# .htaccess o httpd.conf

<IfModule mod_expires.c>
    ExpiresActive On

    # Por defecto: 1 mes
    ExpiresDefault "access plus 1 month"

    # HTML: siempre revalidar
    ExpiresByType text/html "access plus 0 seconds"

    # CSS/JS: 1 año (usar nombres de archivo versionados)
    ExpiresByType text/css "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"

    # Imágenes: 1 año
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"

    # Fuentes: 1 año
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
</IfModule>

<IfModule mod_headers.c>
    # Assets versionados (archivos con hash en nombre)
    <FilesMatch "\.[a-f0-9]{8,}\.(css|js)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>

    # Archivos HTML
    <FilesMatch "\.html$">
        Header set Cache-Control "no-cache"
    </FilesMatch>

    # Habilitar ETag
    FileETag MTime Size
</IfModule>

Configuración de Node.js/Express

const express = require('express');
const path = require('path');

const app = express();

// Middleware personalizado de caché
const setCacheHeaders = (req, res, next) => {
  const url = req.url;

  // Assets versionados (contienen hash)
  if (/\.[a-f0-9]{8,}\.(css|js)$/.test(url)) {
    res.set('Cache-Control', 'public, max-age=31536000, immutable');
  }
  // Imágenes y fuentes
  else if (/\.(jpg|jpeg|png|gif|webp|svg|woff2?|ttf|eot)$/.test(url)) {
    res.set('Cache-Control', 'public, max-age=31536000');
  }
  // HTML
  else if (/\.html$/.test(url) || url === '/') {
    res.set('Cache-Control', 'no-cache');
  }
  // Respuestas de API
  else if (url.startsWith('/api/')) {
    res.set('Cache-Control', 'public, max-age=60, stale-while-revalidate=600');
  }

  next();
};

app.use(setCacheHeaders);

// Servir archivos estáticos con ETags
app.use(express.static('public', {
  etag: true,
  lastModified: true,
  maxAge: 0, // Dejar que el middleware maneje Cache-Control
}));

Estrategia 3: Versionado de Assets (Cache Busting)

Configuración de Herramientas de Build

Webpack
// webpack.config.js
module.exports = {
  output: {
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js',
    assetModuleFilename: 'assets/[name].[contenthash:8][ext]',
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css',
    }),
  ],
};
Vite
// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name].[hash].js',
        chunkFileNames: 'assets/[name].[hash].js',
        assetFileNames: 'assets/[name].[hash].[ext]',
      },
    },
  },
});

Por Qué Importa el Versionado

Sin Versionado:
├── Usuario visita sitio, descarga styles.css
├── Tú actualizas styles.css
├── Usuario revisita - navegador sirve styles.css cacheado (viejo)
├── Usuario ve layout roto hasta que expira caché
└── ¡Mala experiencia de usuario!

Con Versionado:
├── Usuario visita sitio, descarga styles.abc123.css
├── Actualizas CSS, build crea styles.def456.css
├── Usuario revisita - navegador solicita styles.def456.css (¡nuevo!)
├── Usuario siempre ve estilos más recientes
└── ¡Excelente experiencia de usuario!

Estrategia 4: Configuración de Caché en CDN

Cloudflare

Page Rules:
URL: ejemplo.com/assets/*
Settings:
  - Cache Level: Cache Everything
  - Edge Cache TTL: 1 mes
  - Browser Cache TTL: 1 año

URL: ejemplo.com/*.html
Settings:
  - Cache Level: Standard
  - Edge Cache TTL: Respect Existing Headers

AWS CloudFront

{
  "CacheBehaviors": [
    {
      "PathPattern": "/assets/*",
      "DefaultTTL": 31536000,
      "MaxTTL": 31536000,
      "MinTTL": 31536000,
      "Compress": true
    },
    {
      "PathPattern": "*.html",
      "DefaultTTL": 0,
      "MaxTTL": 0,
      "MinTTL": 0
    }
  ]
}

Vercel

// vercel.json
{
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    },
    {
      "source": "/(.*).html",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "no-cache"
        }
      ]
    }
  ]
}

Estrategia 5: Invalidación de Caché

Comparación de Estrategias

Estrategia Cómo Funciona Pros Contras
URLs versionadas Cambiar nombre de archivo Instantáneo, confiable Requiere paso de build
Query strings ?v=123 Fácil de implementar Algunos CDNs ignoran
Purge de CDN Llamada API al CDN Flexible Lento, puede fallar
TTL corto max-age bajo Simple Más solicitudes

Enfoque Recomendado

Mejor Práctica de Invalidación de Caché:
┌─────────────────────────────────────────────────────────────┐
│ Assets Estáticos (CSS, JS, Imágenes):                       │
│ └── Usar content-hash en nombre (invalidación automática)   │
│                                                             │
│ Páginas HTML:                                               │
│ └── Usar no-cache (siempre revalidar)                       │
│                                                             │
│ Respuestas de API:                                          │
│ └── Usar stale-while-revalidate para refresh en background  │
│                                                             │
│ Actualizaciones de Emergencia:                              │
│ └── API de purge del CDN + deploy nuevos assets versionados │
└─────────────────────────────────────────────────────────────┘

Estrategia 6: Header Vary para Caché Condicional

Cuándo Usar Vary

# Cachear diferentes versiones basado en encoding
Vary: Accept-Encoding

# Cachear diferentes versiones para diferentes idiomas
Vary: Accept-Language

# Cachear diferente para usuarios autenticados vs anónimos
Vary: Authorization

# Múltiples condiciones
Vary: Accept-Encoding, Accept-Language

Configuración de Vary en Nginx

# Agregar header Vary para contenido comprimido
location ~* \.(css|js|html|json|xml)$ {
    add_header Vary "Accept-Encoding";
    gzip on;
}

# Agregar Vary para contenido específico por idioma
location /content/ {
    add_header Vary "Accept-Language";
}

Midiendo la Efectividad del Caché

Comparación Antes/Después

ANTES de Optimización de Caché:
├── Carga de Página Repetida: 2.8s
├── Datos Transferidos: 1.9 MB
├── Solicitudes al Servidor: 42
├── Tasa de Acierto de Caché: 15%
└── Lighthouse Performance: 68

DESPUÉS de Optimización de Caché:
├── Carga de Página Repetida: 0.6s (↓79%)
├── Datos Transferidos: 85 KB (↓96%)
├── Solicitudes al Servidor: 8 (↓81%)
├── Tasa de Acierto de Caché: 94%
└── Lighthouse Performance: 96 (↑28 puntos)

Auditorías de Lighthouse

Verifica estos resultados:

  • “Serve static assets with an efficient cache policy” - Debería pasar
  • Muestra recursos con TTL de caché < 1 semana

Pruebas con DevTools

Análisis de Pestaña Network:
1. Habilitar checkbox "Disable cache"
2. Cargar página (simula primera visita)
3. Deshabilitar "Disable cache"
4. Recargar página (simula visita repetida)
5. Comparar columna "Size":
   - "(disk cache)" = cacheado localmente
   - "(memory cache)" = cacheado en memoria
   - Tamaño real = descargado de la red

Lista de Verificación de Optimización

Antes de desplegar, verifica:

  • [ ] Assets estáticos versionados usan max-age=31536000, immutable
  • [ ] HTML usa no-cache para contenido siempre fresco
  • [ ] Imágenes/fuentes usan max-age=31536000
  • [ ] ETags habilitados para revalidación eficiente
  • [ ] Vary: Accept-Encoding configurado para contenido comprimido
  • [ ] Caché de CDN correctamente configurado
  • [ ] Herramienta de build genera content hashes en nombres de archivo
  • [ ] Respuestas de API usan estrategia de caché apropiada
  • [ ] Datos sensibles usan no-store
  • [ ] Auditoría de caché de Lighthouse pasa

Artículos Relacionados

Aprende más sobre optimizar la entrega de recursos:

📚 Volver al Hub de Performance SEO - Explora todos los temas de rendimiento


Referencias

  1. MDN Web Docs - HTTP Caching
  2. web.dev - HTTP Cache
  3. Chrome Developers - Serve Static Assets with Efficient Cache Policy
  4. Google Developers - HTTP Caching

Pruébalo Tú Mismo

¿Quieres verificar los headers de caché de tu sitio?

🔧 Descarga UXR SEO Analyzer (Gratis, análisis 100% local)


Disclaimer: Los analizadores en esta extensión son guías de referencia basadas en documentación oficial de MDN, web.dev y Chrome Developers. No representan verdades absolutas sobre cómo los motores de búsqueda evalúan tu contenido—solo los motores de búsqueda conocen sus algoritmos internos. Usa estas recomendaciones como punto de partida para mejorar tu sitio.

Última actualización: 15 de diciembre de 2025

Artículos relacionados

Hub de categoría

Hub

Hub de SEO de Rendimiento

El rendimiento es un factor de ranking crítico e impacta directamente la experiencia del usuario

En la misma categoría

Guía detallada

Guía Completa de Optimización de TTFB

Time to First Byte (TTFB) es la base del rendimiento web—cada milisegundo de TTFB retrasa toda la carga de tu página

Introducción

Compresión de Texto

La compresión de texto es una de las formas más efectivas de reducir tamaños de archivo y mejorar los tiempos de carga de página

Introducción

Recursos que Bloquean el Renderizado

Los recursos que bloquean el renderizado son archivos que impiden que el navegador muestre contenido a los usuarios hasta que se descarguen y procesen...

Última actualización: