Cache-Control y CORS bien configurados: rendimiento y seguridad para SPAs y APIs
¿Tu SPA o API es lenta y vulnerable? La configuración correcta de Cache-Control y CORS es crucial para el rendimiento y la seguridad. Este artículo te guiará paso a paso para optimizar la entrega de tus recursos y proteger tu aplicación web.
¿Por qué son importantes Cache-Control y CORS?
En el desarrollo de Single Page Applications (SPAs) y APIs, la optimización del rendimiento y la seguridad son fundamentales. Una configuración incorrecta de Cache-Control puede ralentizar la carga de la aplicación y aumentar los costos de infraestructura. Por otro lado, una mala configuración de CORS puede exponer tu API a ataques y comprometer la seguridad de los datos.
La correcta implementación de estas dos tecnologías te permite:
- Mejorar la velocidad de carga: Almacenar en caché los recursos estáticos (como imágenes, archivos CSS y JavaScript) reduce la necesidad de descargarlos cada vez, lo que se traduce en una experiencia de usuario más rápida.
- Reducir la carga del servidor: El almacenamiento en caché alivia la carga de tu servidor, ya que no tiene que procesar las mismas solicitudes repetidamente.
- Prevenir errores de CORS: Configurar CORS de forma segura evita que las solicitudes desde orígenes no autorizados accedan a tus recursos, protegiendo tus datos.
- Mejorar la seguridad: Limitar el acceso a tu API solo a los orígenes permitidos reduce el riesgo de ataques de Cross-Site Scripting (XSS) y otros tipos de vulnerabilidades.
Paso a paso: Configuración correcta de Cache-Control y CORS
A continuación, te presentamos una guía práctica para configurar Cache-Control y CORS en tu SPA y API. Vamos a cubrir tanto la configuración del lado del servidor como ejemplos de código.
Paso 1: Identificar y categorizar tus recursos
El primer paso es identificar los tipos de recursos que tu aplicación utiliza y cómo se actualizan. Esto te ayudará a determinar la estrategia de almacenamiento en caché más adecuada para cada uno.
- Recursos estáticos (assets): Imágenes, archivos CSS, JavaScript, fuentes, etc. Estos recursos generalmente no cambian con frecuencia. Ideal para cacheo de larga duración.
- Recursos dinámicos (API responses y HTML): Datos que cambian con frecuencia, como respuestas de API, contenido HTML generado dinámicamente, etc. Requieren un cacheo más cuidadoso o evitar el cacheo.
Paso 2: Configurar Cache-Control para recursos estáticos
Para los recursos estáticos, la clave es un cacheo de larga duración. Esto se logra con la directiva max-age y, preferiblemente, la directiva immutable. Los assets versionados con hash en su nombre son perfectos para esta estrategia.
# Nginx - cache largo para assets versionados
location ~* \.(js|css|png|jpg|svg|woff|woff2|ttf|eot)$ {
expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
}
Explicación del código:
expires 365d;: Configura la fecha de expiración del caché a un año en el futuro.add_header Cache-Control "public, max-age=31536000, immutable";:public: Indica que el recurso puede ser cacheado por cualquier caché (navegador, CDN, etc.).max-age=31536000: Define el tiempo máximo en segundos que el recurso puede ser almacenado en caché (31536000 segundos = 365 días).immutable: Indica que el recurso no cambiará. Esto permite a los navegadores ser más agresivos con el almacenamiento en caché, ya que saben que el recurso no se modificará. Es crucial para assets con hash en su nombre.
Paso 3: Configurar Cache-Control para recursos dinámicos
Para las respuestas de la API y el contenido HTML dinámico, necesitas un enfoque diferente. Generalmente, querrás un cacheo más corto o evitar el cacheo por completo, dependiendo del caso de uso.
- Respuestas de API:
no-cache: El navegador debe validar el recurso con el servidor antes de usarlo. Esto asegura que el cliente siempre obtenga la versión más reciente, pero requiere una solicitud al servidor cada vez.no-store: El recurso no debe ser almacenado en caché en ningún lugar (navegador, proxy, etc.). Útil para datos sensibles.max-age(corto): Establece un tiempo de expiración corto (por ejemplo, 60 segundos) para las respuestas que pueden ser actualizadas con frecuencia.- HTML: Generalmente, el HTML de tu SPA no debería ser cacheado por mucho tiempo. Considera usar
no-cacheono-store.
# Nginx - Cache-Control para HTML dinámico (ejemplo)
location / {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
Explicación del código:
no-cache: El navegador debe revalidar el recurso con el servidor antes de usarlo.no-store: El recurso no debe ser almacenado en caché.must-revalidate: El caché debe revalidar el recurso con el servidor después de que expire (conmax-age).
Paso 4: Configurar CORS
CORS (Cross-Origin Resource Sharing) es un mecanismo que permite a los navegadores web realizar solicitudes a recursos de diferentes orígenes (dominios, protocolos o puertos). Es fundamental para la seguridad de tu API.
Reglas básicas:
Access-Control-Allow-Origin: Especifica qué orígenes están autorizados a acceder a tus recursos. Nunca uses*(permitir todos) si tu API utiliza cookies, autenticación o cualquier información sensible.Access-Control-Allow-Credentials: Permite que las solicitudes con credenciales (cookies, encabezados de autorización, etc.) sean aceptadas. Debe ir junto con unAccess-Control-Allow-Originespecífico, no*.Access-Control-Allow-Methods: Especifica los métodos HTTP permitidos (GET, POST, PUT, DELETE, etc.).Access-Control-Allow-Headers: Especifica los encabezados HTTP personalizados permitidos en las solicitudes. Necesario para solicitudes que envían encabezados distintos de los predeterminados.Vary: Origin: Indica al caché que varíe la respuesta en función del encabezadoOrigin. Es crucial para que el caché funcione correctamente cuando se sirve contenido diferente a diferentes orígenes.
# Nginx - CORS seguro (ejemplo)
add_header Access-Control-Allow-Origin "https://app.tudominio.com" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;
add_header Vary "Origin" always;
Explicación del código:
Access-Control-Allow-Origin "https://app.tudominio.com": Permite solicitudes solo desdehttps://app.tudominio.com. Reemplaza con tu dominio.Access-Control-Allow-Credentials "true": Permite el uso de credenciales (cookies, etc.). Necesita unAccess-Control-Allow-Originespecífico.Access-Control-Allow-Methods "GET, POST, OPTIONS": Permite los métodos GET, POST y OPTIONS.Access-Control-Allow-Headers: Permite ciertos headers. Debes agregar los headers que necesites, incluyendoAuthorizationsi usas autenticación.Vary "Origin": Esencial para que el caché se comporte correctamente con CORS.
Ejemplo con Express.js:
const express = require('express');
const cors = require('cors');
const app = express();
const allowedOrigins = ['https://app.tudominio.com', 'http://localhost:3000'];
const corsOptions = {
origin: function (origin, callback) {
if (allowedOrigins.indexOf(origin) !== -1 || !origin) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
credentials: true, // Habilitar el envío de cookies y encabezados de autorización
methods: 'GET, POST, PUT, DELETE, OPTIONS',
allowedHeaders: 'Content-Type, Authorization'
};
app.use(cors(corsOptions));
Explicación del código (Express.js):
- Se utiliza el middleware
corspara configurar CORS. origin: Una función que determina si el origen de la solicitud está permitido.credentials: true: Permite el envío de credenciales.methods: Define los métodos HTTP permitidos.allowedHeaders: Define los encabezados permitidos.
Paso 5: Manejo de las solicitudes OPTIONS (preflight)
Cuando el navegador realiza una solicitud CORS que no es un "request simple" (por ejemplo, una solicitud POST con un encabezado Content-Type que no sea application/x-www-form-urlencoded, multipart/form-data, o text/plain), primero envía una solicitud "preflight" con el método OPTIONS para determinar si la solicitud real es segura.
Tu servidor debe responder correctamente a estas solicitudes OPTIONS con los encabezados CORS adecuados. Si no lo hace, el navegador bloqueará la solicitud.
# Nginx - Respuesta a OPTIONS (ejemplo)
if ($request_method = OPTIONS) {
add_header "Access-Control-Allow-Origin" "$http_origin";
add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS";
add_header "Access-Control-Allow-Headers" "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
add_header "Access-Control-Allow-Credentials" "true";
add_header "Content-Length" 0;
add_header "Content-Type" "text/plain charset=UTF-8";
return 204;
}
Explicación del código:
- Verifica si el método de la solicitud es
OPTIONS. - Agrega los encabezados CORS necesarios, asegurándose de que coincidan con los de las solicitudes reales. En particular,
Access-Control-Allow-Origindebe reflejar el valor del encabezadoOriginde la solicitud. - Devuelve el código de estado
204 No Contentpara indicar que la solicitud OPTIONS se ha procesado correctamente. Content-Length: 0: Indica que no hay cuerpo en la respuesta.
Paso 6: Validar la configuración
Después de configurar Cache-Control y CORS, es crucial verificar que la configuración se está aplicando correctamente. Utiliza las siguientes herramientas y técnicas:
- Herramientas de desarrollo del navegador: Inspecciona la pestaña "Network" de las herramientas de desarrollo de tu navegador (Chrome DevTools, Firefox Developer Tools, etc.) para ver los encabezados HTTP que se envían y reciben.
- Analizadores de encabezados HTTP: Utiliza herramientas en línea como el Analizador de Headers HTTP Pro (recomendado más adelante) para verificar la configuración de
Cache-Controly CORS. - Pruebas exhaustivas: Realiza pruebas exhaustivas de tu aplicación desde diferentes orígenes para asegurarte de que CORS funciona correctamente.
Checklist final para producción
Antes de implementar en producción, asegúrate de haber completado la siguiente checklist:
- [ ] Assets estáticos con cacheo de larga duración: Configura
Cache-Control: public, max-age=31536000, immutablepara assets versionados con hash en su nombre. - [ ] HTML dinámico con cacheo adecuado: Usa
Cache-Control: no-cache, no-store, must-revalidateo similar. - [ ] CORS seguro:
Access-Control-Allow-Originespecífico,Access-Control-Allow-Credentials: true(si usas cookies) yVary: Origin. - [ ] Manejo de OPTIONS: Asegúrate de responder correctamente a las solicitudes
OPTIONScon los encabezados CORS correctos. - [ ] Pruebas exhaustivas: Verifica la configuración de Cache-Control y CORS en producción con herramientas de desarrollo y analizadores de headers.
Errores comunes y cómo evitarlos
Aquí te presentamos algunos errores comunes al configurar Cache-Control y CORS, junto con soluciones:
- Cachear HTML de la SPA y servir versiones antiguas:
Problema: Los usuarios pueden ver versiones desactualizadas de tu aplicación después de un despliegue.
Solución: Utiliza
Cache-Control: no-cache, no-store, must-revalidatepara tu HTML, o implementa un sistema de invalidación de caché (por ejemplo, usando un hash de la versión en el nombre del archivo HTML). - CORS demasiado abierto:
Problema: Permitir el acceso desde cualquier origen (
Access-Control-Allow-Origin: *cuando se utilizan credenciales) puede exponer tu API a ataques.Solución: Siempre especifica los orígenes permitidos de forma explícita. Nunca uses
*si tu API utiliza cookies, autenticación o cualquier información sensible. - Olvidar
Vary: Origin:Problema: El caché podría servir la misma respuesta a diferentes orígenes, lo que podría provocar errores de CORS.
Solución: Asegúrate de incluir
Vary: Originen tus respuestas CORS. - No responder a
OPTIONS:Problema: El navegador bloqueará las solicitudes CORS que requieren una preflight (OPTIONS).
Solución: Configura tu servidor para responder correctamente a las solicitudes
OPTIONScon los encabezados CORS correctos. - ETag inconsistente por compresión/servidores diferentes:
Problema: Si usas ETag para el cacheo de estáticos, asegúrate de que el ETag sea consistente en diferentes servidores y configuraciones de compresión. De lo contrario, los clientes podrían no usar la caché correctamente.
Solución: Configura la misma compresión y otras opciones de procesamiento en todos los servidores, o considera usar
Last-Modifieden lugar de ETag para assets estáticos. Si usas CDN, verifica que manejen ETag de forma consistente.
Preguntas frecuentes
¿Cache-Control afecta al SEO?
Sí, en cierta medida. Un servidor lento impacta el crawl budget de Google. Cache-Control ayuda a mejorar el rendimiento, lo que puede influir indirectamente en el SEO.
¿CORS afecta a herramientas como curl o Postman?
No directamente. CORS es una restricción implementada por los navegadores. curl, Postman y otras herramientas de línea de comandos no están sujetas a las restricciones de CORS. Sin embargo, una configuración de CORS segura y consistente es importante para la seguridad y el correcto funcionamiento de tu API, independientemente de la herramienta que se utilice.
¿Cuándo debo usar immutable?
Usa immutable en assets estáticos con nombres que incluyen un hash (por ejemplo, imagen-12345.png). Esto le indica al navegador que el recurso no cambiará, permitiendo un cacheo más agresivo. Si no utilizas hashes en los nombres de los archivos, no uses immutable, ya que podrías dejar a los usuarios con versiones antiguas en caché.
¿Cómo puedo forzar una actualización de la caché en el navegador?
Hay varias formas de forzar una actualización de la caché:
- Hard Refresh (Ctrl+Shift+R o Cmd+Shift+R): Esto suele ignorar la caché y recargar la página.
- Vaciar la caché del navegador: En la configuración del navegador, puedes borrar los archivos y la caché de imágenes.
- Cambiar el nombre del archivo: Si cambias el nombre del archivo (por ejemplo,
script.jsascript-v2.js), el navegador lo tratará como un nuevo recurso y lo descargará. Este es el enfoque recomendado para assets estáticos. - Usar un parámetro de consulta (queryString): Agregar un parámetro de consulta al final de la URL (por ejemplo,
script.js?v=2) también puede forzar al navegador a descargar el archivo de nuevo. Sin embargo, esto no es tan eficiente como el cambio de nombre del archivo y puede afectar el cacheo de proxies y CDN.
Herramientas recomendadas
Para verificar y depurar la configuración de Cache-Control y CORS, utiliza las siguientes herramientas:
- Analizador Headers Http Pro: Esta herramienta te permite analizar los encabezados HTTP de tus respuestas y verificar la configuración de
Cache-Controly CORS. - Herramientas de desarrollo del navegador (Chrome DevTools, Firefox Developer Tools): Útiles para inspeccionar los encabezados HTTP en tiempo real y diagnosticar problemas.
Recomendaciones según el perfil
La configuración óptima de Cache-Control y CORS depende de tu caso de uso específico. Aquí hay algunas recomendaciones:
- Desarrolladores de SPAs: Prioriza el cacheo de larga duración para assets estáticos con nombres con hash (
immutable). Configura CORS de forma segura para permitir solicitudes desde tu dominio. Presta especial atención a la invalidación de la caché para HTML dinámico. - Desarrolladores de APIs: Utiliza
no-cacheono-storepara respuestas dinámicas, y establece unmax-agecorto si la información se actualiza con frecuencia. Configura CORS de forma rigurosa, limitando el acceso a los orígenes permitidos y usandoAccess-Control-Allow-Credentials: truesolo si es absolutamente necesario. - Equipos de DevOps: Automatiza la configuración de
Cache-Controly CORS como parte de tu proceso de despliegue. Utiliza herramientas de monitorización para detectar problemas de rendimiento y seguridad relacionados con el caché y CORS.