CORS (Cross-Origin Resource Sharing) errors are one of the most frustrating issues developers face when building web applications. If you've seen the error "Access to fetch at '...' from origin '...' has been blocked by CORS policy," you know what I mean.
What is CORS?
CORS is a security mechanism implemented by browsers that restricts web pages from making requests to a different domain, protocol, or port than the one serving the web page.
Example:
- Your frontend is at: `https://myapp.com`
- Your API is at: `https://api.myapp.com`
- Browser blocks the request because they're different origins
Why CORS Errors Happen
CORS errors occur when:
1. Different domains: Frontend and backend are on different domains
2. Different ports: Frontend on port 3000, backend on port 5000
3. Different protocols: HTTP vs HTTPS
4. Missing CORS headers: Backend doesn't send proper CORS headers
Common CORS Error Messages
Access to fetch at 'https://api.example.com/data' from origin 'https://myapp.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.How to Fix CORS Errors
Solution 1: Enable CORS on Your Backend (Recommended)
For Express.js/Node.js:
// Install cors package
npm install cors
// Basic setup
const express = require('express');
const cors = require('cors');
const app = express();
// Enable CORS for all routes
app.use(cors());
app.get('/api/data', (req, res) => {
res.json({ message: 'Hello from API' });
});
app.listen(5000);Configure CORS with options:
const cors = require('cors');
const corsOptions = {
origin: 'https://myapp.com', // Allow only your frontend
// Or allow multiple origins
// origin: ['https://myapp.com', 'https://www.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'], // Allowed methods
allowedHeaders: ['Content-Type', 'Authorization'], // Allowed headers
credentials: true, // Allow cookies/credentials
};
app.use(cors(corsOptions));For development (allow all origins):
const corsOptions = {
origin: process.env.NODE_ENV === 'production'
? 'https://myapp.com'
: true, // Allow all origins in development
credentials: true,
};
app.use(cors(corsOptions));Solution 2: Manual CORS Headers (Without Package)
app.use((req, res, next) => {
// Allow requests from your frontend
res.header('Access-Control-Allow-Origin', 'https://myapp.com');
// Allow credentials (cookies, authorization headers)
res.header('Access-Control-Allow-Credentials', 'true');
// Allow specific methods
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
// Allow specific headers
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Handle preflight requests
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});Solution 3: Next.js API Routes (No CORS Needed)
If you're using Next.js, use API routes as a proxy:
// pages/api/proxy.ts
export default async function handler(req, res) {
// Next.js API routes are same-origin, so no CORS needed
const response = await fetch('https://external-api.com/data', {
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`
}
});
const data = await response.json();
res.json(data);
}
// In your frontend
const response = await fetch('/api/proxy'); // Same origin, no CORS!Solution 4: Fix CORS in React/Next.js Frontend
Using Next.js API Routes (Best Practice):
// pages/api/users.ts
export default async function handler(req, res) {
// This runs server-side, so no CORS issues
const response = await fetch('https://external-api.com/users', {
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`
}
});
const data = await response.json();
res.json(data);
}
// In your component
const fetchUsers = async () => {
const response = await fetch('/api/users'); // Same origin!
const users = await response.json();
return users;
};If you must call external API directly:
// Only works if the API has CORS enabled
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // Include cookies if needed
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
return data;
} catch (error) {
console.error('CORS or fetch error:', error);
}
};Solution 5: Development Proxy (Create React App)
If using Create React App, add proxy to package.json:
{
"name": "my-app",
"proxy": "http://localhost:5000"
}Then in your code:
// Instead of http://localhost:5000/api/data
fetch('/api/data') // Proxy handles itSolution 6: Handle Preflight Requests
Browsers send OPTIONS requests (preflight) before certain requests:
// Handle preflight requests
app.options('*', cors()); // Handle all OPTIONS requests
// Or handle specific route
app.options('/api/data', cors());Common CORS Scenarios
Scenario 1: Frontend and Backend on Different Ports
Problem:
- Frontend: `http://localhost:3000`
- Backend: `http://localhost:5000`
Solution:
// Backend (Express)
const cors = require('cors');
app.use(cors({
origin: 'http://localhost:3000',
credentials: true
}));Scenario 2: Production Deployment
Problem:
- Frontend: `https://myapp.com`
- Backend: `https://api.myapp.com`
Solution:
const corsOptions = {
origin: [
'https://myapp.com',
'https://www.myapp.com'
],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));Scenario 3: Third-Party API Without CORS
Problem:
External API doesn't allow CORS, and you can't modify it.
Solution:
Use a backend proxy (Next.js API route or Express server):
// Backend proxy
app.get('/api/proxy', async (req, res) => {
const response = await fetch('https://external-api.com/data', {
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`
}
});
const data = await response.json();
res.json(data);
});
// Frontend calls your proxy
fetch('/api/proxy'); // No CORS issue!Testing CORS
Test with curl:
# Check if CORS headers are present
curl -H "Origin: https://myapp.com" -H "Access-Control-Request-Method: GET" -H "Access-Control-Request-Headers: Content-Type" -X OPTIONS https://api.example.com/data -vCheck response headers:
- `Access-Control-Allow-Origin`
- `Access-Control-Allow-Methods`
- `Access-Control-Allow-Headers`
Security Best Practices
1. Don't use origin: '*' in production
- - Only allow specific origins
- - Use environment variables
2. Limit allowed methods and headers
- - Only allow what you need
3. Use credentials carefully
- - Only enable if you need cookies/auth headers
4. Validate origin on server
- - Don't trust client-side validation
const allowedOrigins = [
'https://myapp.com',
'https://www.myapp.com'
];
const corsOptions = {
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
};Common Mistakes
1. Forgetting to handle OPTIONS requests
2. Using origin: '*' with credentials
3. Not setting credentials: true when needed
4. Mismatched origins (http vs https)
5. Not testing in production environment
Conclusion
CORS errors are common but fixable. The key points:
1. Enable CORS on your backend with proper configuration
2. Use Next.js API routes as a proxy when possible
3. Never use origin: '*' in production
4. Handle preflight (OPTIONS) requests
5. Test CORS in your deployment environment
Remember: CORS is a browser security feature. It can't be bypassed from the frontend—you must configure it on the backend.