CORS: El Error Que Todo Desarrollador Ha Visto
Estás construyendo un frontend que llama a tu API. Funciona en Postman. Funciona con curl. Pero el navegador dice:
Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.Esto no es un error del servidor. Es el navegador protegiendo al usuario.
Qué Es Realmente CORS
CORS (Cross-Origin Resource Sharing) es un mecanismo de seguridad del navegador. Por defecto, JavaScript corriendo en origen A no puede hacer solicitudes a origen B. Un origen es la combinación de protocolo + dominio + puerto:
| URL | Origen |
|---|---|
http://localhost:3000 | http://localhost:3000 |
https://miapp.com | https://miapp.com |
https://api.miapp.com | https://api.miapp.com (subdominio diferente = origen diferente) |
https://miapp.com:8080 | https://miapp.com:8080 (puerto diferente = origen diferente) |
Cómo Funciona
Para solicitudes simples (GET, POST con content types estándar), el navegador envía la solicitud normalmente pero verifica los headers de la respuesta. Si la respuesta no incluye Access-Control-Allow-Origin, el navegador bloquea a JavaScript de leer la respuesta.
Para solicitudes complejas (PUT, DELETE, headers personalizados, content type JSON), el navegador envía una solicitud preflight primero:
OPTIONS /api/data HTTP/1.1
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, AuthorizationEl servidor debe responder con:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400Solucionando CORS
Express.js:
import cors from 'cors';
// Permitir todos los orígenes (¡solo desarrollo!)
app.use(cors());
// Producción: especificar orígenes permitidos
app.use(cors({
origin: ['https://miapp.com', 'https://staging.miapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // si usas cookies
}));API routes Next.js:
export async function GET(request: Request) {
const data = await getData();
return Response.json(data, {
headers: {
'Access-Control-Allow-Origin': 'https://miapp.com',
'Access-Control-Allow-Methods': 'GET, POST',
},
});
}Nginx:
location /api/ {
add_header 'Access-Control-Allow-Origin' 'https://miapp.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
}Errores Comunes
- Usar
*con credentials.Access-Control-Allow-Origin: *no funciona concredentials: true. Debes especificar el origen exacto. - Olvidar el handler de OPTIONS. Tu servidor debe responder a solicitudes preflight OPTIONS con headers CORS y status 204.
- Resolver en el frontend. CORS es configuración server-side. Ningún código frontend puede eludirlo (ese es el objetivo).
- Proxy en dev, olvidar en prod. Rewrites de
next.config.jsfuncionan en desarrollo pero no en producción. Necesitas headers CORS reales en el servidor de la API.
Alternativas a CORS
- Proxy same-origin: Enruta llamadas de API a través de tu propio servidor (
/api/proxy) para que el navegador vea solicitudes same-origin. - Server-side rendering: Busca datos en el servidor (Next.js Server Components), no en el navegador. Sin necesidad de CORS.
Depura tus headers: Referencia HTTP — verifica status codes y headers de tus respuestas de API.