NITAI IT SOLUTION

How to Fix CORS Errors in Node.js, Express, and Next.js: The Complete Developer Guide

A definitive troubleshooting guide for resolving Cross-Origin Resource Sharing (CORS) errors: preflight OPTIONS requests, Access-Control-Allow-Origin, and credentials.

Every web developer has stared in frustration at the dreaded browser console error: "Access to fetch at 'https://api.example.com' from origin 'https://app.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource." Contrary to common belief, CORS is not a bug or a server crash; it is a critical browser-enforced security mechanism designed to protect users from malicious cross-site data theft. This guide deconstructs how CORS works and provides battle-tested production fixes for Node.js, Express, Next.js, and Nginx.

What Is CORS and Why Does the Browser Block Requests?

The Same-Origin Policy (SOP) is a foundational browser security standard. An origin is defined by the exact combination of Protocol + Domain + Port:

Without CORS restrictions, a malicious website you visit in another tab could run JavaScript in your browser to send background requests to yourbank.com/api/transfer, automatically attaching your saved session cookies.

Simple Requests vs. Preflight OPTIONS Requests

The browser categorizes HTTP requests into two types:

Request Category Conditions Required Browser Behavior
Simple Requests Methods: GET, HEAD, or POST with standard Content-Type (application/x-www-form-urlencoded, multipart/form-data, text/plain) Browser sends request immediately, then checks if server response includes Access-Control-Allow-Origin.
Preflighted Requests Methods: PUT, DELETE, PATCH, or custom headers (Authorization, X-Api-Key), or application/json Browser pauses! It sends an initial lightweight HTTP OPTIONS preflight probe asking the server for permission before sending the actual request.

How to Fix CORS in Express / Node.js (The Right Way)

A dangerous anti-pattern is blindly configuring app.use(cors({ origin: '*' })). If your frontend needs to send authentication cookies or authorization headers (credentials: 'include'), browsers will reject wildcards:

// server.ts (Express with Production-Grade CORS)
import express from 'express';
import cors from 'cors';

const app = express();

const allowedOrigins = [
  'https://nitaiitsolution.in',
  'https://app.nitaiitsolution.in',
  'http://localhost:3000' // Local development
];

app.use(cors({
  origin: (origin, callback) => {
    // Allow server-to-server or tools like Postman (where origin is undefined)
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Blocked by CORS policy: Unauthorized Origin'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'X-Api-Key'],
  credentials: true, // Crucial for HttpOnly cookies and authorization headers!
  maxAge: 86400 // Cache preflight OPTIONS response for 24 hours to reduce latency!
}));

// Explicitly handle preflight across all routes
app.options('*', cors());

Fixing CORS in Next.js 15 (Route Handlers & Config)

If you are building an API within Next.js App Router, you can handle CORS globally via next.config.ts headers:

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          { key: 'Access-Control-Allow-Credentials', value: 'true' },
          { key: 'Access-Control-Allow-Origin', value: 'https://nitaiitsolution.in' },
          { key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE,OPTIONS' },
          { key: 'Access-Control-Allow-Headers', value: 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Authorization' },
        ],
      },
    ];
  },
};

export default nextConfig;

Bypassing CORS via Nginx Reverse Proxy

The cleanest production architecture avoids cross-origin requests entirely by having an Nginx reverse proxy route both frontend assets and backend API endpoints under the same root domain:

# /etc/nginx/sites-available/default
server {
    server_name nitaiitsolution.in;

    # Frontend web application
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Backend API (Same origin! Zero CORS issues!)
    location /api/ {
        proxy_pass http://localhost:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Frequently Asked Questions

Why does Postman work, but the browser gives a CORS error?

CORS is enforced exclusively by web browsers. Backend tools like Postman, curl, or mobile native apps do not enforce the browser Same-Origin Policy, which is why API calls succeed in testing tools but fail inside Chrome or Safari.

Can I use Access-Control-Allow-Origin: * with cookies?

No. The W3C specification strictly prohibits using the wildcard * when Access-Control-Allow-Credentials: true is present. You must dynamically validate and reflect the specific request origin in the header.

What does Access-Control-Max-Age do?

It tells the browser how many seconds it can cache the result of the preflight OPTIONS request. Setting maxAge: 86400 (24 hours) prevents the browser from sending duplicate OPTIONS requests before every single API call, slashing network latency.

Need Help Implementing This in Production?

Our senior engineering team helps startups and enterprises scale web applications, migrate cloud infrastructure, and build custom AI workflows with guaranteed delivery timelines.

Schedule Free Technical Consultation Chat with Lead Architect