Ver contenido
Guía Completa de Configuración de Headers de Caché HTTP para Rendimiento Web
Introducción
El caché HTTP es una de las optimizaciones de rendimiento más poderosas disponibles. Un caché correctamente configurado puede eliminar solicitudes de red por completo, reduciendo los tiempos de carga de página de segundos a milisegundos para visitantes recurrentes. Esta guía proporciona estrategias completas de implementación para diferentes entornos de servidor.
¿Nuevo en caché? Comienza con nuestra guía para principiantes: 📖 Leer: Headers de Caché Explicado
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-cachepara contenido siempre fresco - [ ] Imágenes/fuentes usan
max-age=31536000 - [ ] ETags habilitados para revalidación eficiente
- [ ]
Vary: Accept-Encodingconfigurado 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
Continúa optimizando la entrega de tus recursos:
- Headers de Caché Explicado - Entiende los fundamentos
- Guía de Optimización de TTFB - El caché elimina TTFB
- Guía de Implementación de Compresión - Combinar con caché
- Guía de Recursos Bloqueantes - Cachear recursos críticos
📚 Volver al Hub de Performance SEO - Explora todos los temas de rendimiento
Referencias
- MDN Web Docs - HTTP Caching
- web.dev - HTTP Cache
- Chrome Developers - Serve Static Assets with Efficient Cache Policy
- Google Developers - HTTP Caching
Pruébalo Tú Mismo
¿Listo para optimizar tus headers de caché?
🔧 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