HeoLab
ToolsBlogAboutContact
HeoLab

Free developer tools with AI enhancement. Built for developers who ship.

Tools

  • JSON Formatter
  • JWT Decoder
  • Base64 Encoder
  • Timestamp Converter
  • Regex Tester
  • All Tools →

Resources

  • Blog
  • What is JSON?
  • JWT Deep Dive
  • Base64 Explained

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 HeoLab. All rights reserved.

Tools work in your browser. Zero data retention.

HomeBlogUnderstanding CORS: Why Browsers Block Your Requests and How to Fix It
Table of Contents▾
  • What Is CORS?
  • Why CORS Exists
  • How CORS Actually Works
  • Simple Requests
  • Preflight Requests
  • Fixing CORS on the Server
  • Express.js
  • Next.js API Routes
  • Or use Next.js middleware
  • The Credentials Trap
  • Debugging CORS
  • Simulate a CORS preflight
  • Common CORS Mistakes
guides#cors#security#http

Understanding CORS: Why Browsers Block Your Requests and How to Fix It

A comprehensive guide to Cross-Origin Resource Sharing — how it works, why it exists, and practical solutions for every CORS error you'll encounter.

Trong Ngo
February 28, 2026
4 min read

CORS errors are one of the most common frustrations in web development. You make an API call, and suddenly the browser throws a red error: "Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy." This guide explains why this happens and how to fix it correctly.

What Is CORS?

CORS (Cross-Origin Resource Sharing) is a browser security feature that restricts HTTP requests made from JavaScript in a web page to a different origin than the page's own origin.

An origin is the combination of:

  • Protocol: http:// vs https://
  • Domain: example.com vs api.example.com
  • Port: :3000 vs :8080

Any difference in these three makes it "cross-origin":

Request fromToCross-origin?
http://example.comhttps://example.com✅ Yes (different protocol)
http://example.comhttp://api.example.com✅ Yes (different subdomain)
http://example.com:3000http://example.com:4000✅ Yes (different port)
http://example.com/page1http://example.com/api❌ No (same origin)

Why CORS Exists

Without CORS, a malicious website could use your logged-in session to make requests to your bank's API:

// Without CORS, evil.com could do this:
fetch('https://yourbank.com/api/transfer', {
  method: 'POST',
  credentials: 'include', // sends your bank cookies
  body: JSON.stringify({ to: 'attacker', amount: 10000 })
});

CORS prevents this by having the server declare which origins are allowed to make requests to it.

How CORS Actually Works

Simple Requests

Some requests are "simple" (GET/HEAD/POST with common headers and content types). The browser sends the request with an Origin header and checks the response:

Request:  Origin: http://localhost:3000
Response: Access-Control-Allow-Origin: http://localhost:3000  ✅ allowed
Response: Access-Control-Allow-Origin: *                       ✅ allowed
Response: (no header)                                          ❌ blocked

Preflight Requests

For requests with custom headers, methods like PUT/DELETE, or non-standard content types, the browser sends a preflight OPTIONS request first:

OPTIONS /api/users HTTP/1.1
Origin: http://localhost:3000
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization, content-type

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Max-Age: 86400

If the preflight fails, the actual request is never sent.

Fixing CORS on the Server

The correct fix is always server-side. Never add a browser extension or proxy hack in production.

Express.js

const cors = require('cors');

// Allow all origins (development only!)
app.use(cors());

// Allow specific origins
app.use(cors({
  origin: ['https://yourapp.com', 'http://localhost:3000'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Authorization', 'Content-Type'],
  credentials: true, // if you need cookies/auth
}));

Next.js API Routes

// app/api/data/route.ts
export async function GET(req: Request) {
  const data = await getData();
  return new Response(JSON.stringify(data), {
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': 'https://yourapp.com',
    },
  });
}

// Handle preflight
export async function OPTIONS() {
  return new Response(null, {
    headers: {
      'Access-Control-Allow-Origin': 'https://yourapp.com',
      'Access-Control-Allow-Methods': 'GET, POST',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    },
  });
}

Or use Next.js middleware

// middleware.ts
import { NextResponse } from 'next/server';

export function middleware(request: Request) {
  const response = NextResponse.next();
  response.headers.set('Access-Control-Allow-Origin', '*');
  return response;
}

export const config = { matcher: '/api/:path*' };

The Credentials Trap

Using credentials: 'include' to send cookies? The Access-Control-Allow-Origin: * wildcard doesn't work — you must specify the exact origin:

// Client
fetch('/api/me', { credentials: 'include' });

// Server — '*' will NOT work with credentials
// 'Access-Control-Allow-Origin': '*'        ❌
// 'Access-Control-Allow-Origin': 'http://localhost:3000'  ✅
// Also required:
// 'Access-Control-Allow-Credentials': 'true'

Debugging CORS

  1. Open DevTools → Network → find the failed request
  2. Check the Response Headers tab for Access-Control-Allow-Origin
  3. Check the Console for the exact error message
  4. If a preflight failed, look for the OPTIONS request and check its response

Use curl to test without browser CORS restrictions:

# Simulate a CORS preflight
curl -X OPTIONS https://api.example.com/data \
  -H 'Origin: http://localhost:3000' \
  -H 'Access-Control-Request-Method: GET' \
  -v

Use HeoLab's HTTP Header Viewer to inspect the headers your server is actually returning, and HeoLab's Curl Converter to build curl commands from fetch code.

Common CORS Mistakes

  1. Setting CORS headers on redirected responses — set them on the final response
  2. Not handling OPTIONS preflight — return 200 with CORS headers
  3. Using wildcard with credentials — use specific origin instead
  4. Setting headers in frontend code — CORS headers must come from the server
  5. Fixing CORS with a proxy in dev, forgetting production — configure CORS properly on the API

Try These Tools

HTTP Header Viewer

View all HTTP request headers sent by your browser. Inspect User-Agent, cookies, Accept, and more.

cURL Converter

Convert curl commands to fetch, axios, HTTPie, and other HTTP client formats.

Related Articles

API Key Security: Design, Storage, and Rotation Best Practices

4 min read

HMAC Authentication: Signing API Requests the Right Way

4 min read

YAML vs JSON: When to Use Each and How to Convert Between Them

3 min read

Back to Blog

Table of Contents

  • What Is CORS?
  • Why CORS Exists
  • How CORS Actually Works
  • Simple Requests
  • Preflight Requests
  • Fixing CORS on the Server
  • Express.js
  • Next.js API Routes
  • Or use Next.js middleware
  • The Credentials Trap
  • Debugging CORS
  • Simulate a CORS preflight
  • Common CORS Mistakes

Related Articles

API Key Security: Design, Storage, and Rotation Best Practices

4 min read

HMAC Authentication: Signing API Requests the Right Way

4 min read

YAML vs JSON: When to Use Each and How to Convert Between Them

3 min read