TidyURLs
Go to Tools
BLOG
The TidyURLsJournal

Practical guides, tutorials, and insights about URLs, SEO, web development, and online marketing. Learn, troubleshoot, and get the most of the web.

Back to the Journal
TidyURLs Journal

How to Fix CORS Errors in a Next.js and Express App

Practical insights, clear explanations, and useful ideas for understanding the web.

October 26, 20266 min read6 topics
How to Fix CORS Errors in a Next.js and Express App
Explore the details
#CORS#Next.js#Express#JavaScript#API#Troubleshooting
October 26, 20266 min read

A CORS error in a Next.js and Express app usually means the browser did not receive the cross-origin headers it requires from the Express API. The fix belongs on the server that sends the response—not in a client-side workaround. This guide shows how to identify the failing request, configure Express, handle preflight requests, and account for credentials and server-side Next.js requests.

Key Takeaways

  • An origin is the combination of scheme, hostname, and port. http://localhost:3000 and http://localhost:4000 are different origins.
  • CORS is enforced by browsers. Server-to-server requests, including requests made by Next.js on the server, are not blocked by browser CORS checks.
  • A browser may send an OPTIONS preflight before the actual request. The API must allow the requested method and headers.
  • Credentialed browser requests need an explicit allowed origin and Access-Control-Allow-Credentials: true; * cannot be used as the allowed origin for them.

What a CORS error means

Cross-Origin Resource Sharing (CORS) is a browser mechanism that controls whether JavaScript from one origin can read a response from another. An origin consists of the scheme, hostname, and port. For example, a Next.js page at http://localhost:3000 and an Express API at http://localhost:4000 are cross-origin even though both use localhost.

CORS is not authentication, and it does not generally stop a request from reaching the server. It determines whether the browser makes the response available to the requesting page. A server-to-server request does not rely on the browser's CORS permission check.

Check whether the request is made in the browser

First identify where the request runs:

  • Client Component or browser code: the browser applies CORS. Configure the Express response.
  • Next.js server code: the request runs on the server, so browser CORS does not apply to that server-to-server hop. You may still need valid authentication, network access, and the right API URL.

For a browser request, open the browser developer tools and inspect the Network panel. Look for an OPTIONS request immediately before the API call. If it fails, or its response lacks the required CORS headers, the browser will not proceed with—or expose the result of—the actual request.

Configure Express to allow the Next.js origin

Install the Express CORS middleware in the API project:

npm install cors

Then register it before your routes. This example allows a Next.js app running at http://localhost:3000, including browser requests that use credentials:

const express = require("express");
const cors = require("cors");

const app = express();

app.use(
  cors({
    origin: "http://localhost:3000",
    credentials: true,
    methods: ["GET", "POST", "OPTIONS"],
    allowedHeaders: ["Content-Type", "Authorization"],
  })
);

app.use(express.json());

app.get("/api/profile", (req, res) => {
  res.json({ name: "Ada" });
});

app.post("/api/profile", (req, res) => {
  res.json({ received: req.body });
});

app.listen(4000, () => {
  console.log("API listening on http://localhost:4000");
});

Application-level cors() middleware handles preflight requests for routes under it. Put it before the routes so it can respond to the browser's OPTIONS request as well as add CORS headers to the actual response.

Replace the development origin with the exact origin of your deployed Next.js app. An origin has no path or trailing route: https://app.example.com is an origin; https://app.example.com/dashboard is not. A different scheme or port is also a different origin.

Preflight requests and allowed headers

A browser sends a preflight OPTIONS request when a cross-origin request needs permission before it can make the actual request. This commonly happens with methods such as PUT or DELETE, a JSON POST using Content-Type: application/json, or custom headers such as Authorization.

The browser's preflight includes the intended method and headers. Express must allow them. In the example, methods allows the listed methods and allowedHeaders permits Content-Type and Authorization. If your request uses another non-safelisted header, add that header to the allowlist. A preflight failure is often caused by a missing method or header—not by the route's response body.

Make the browser request match the server settings

For example, a Client Component can make a JSON request with an authorization header:

"use client";

export async function saveProfile(token, profile) {
  const response = await fetch("http://localhost:4000/api/profile", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify(profile),
  });

  if (!response.ok) {
    throw new Error(`API request failed: ${response.status}`);
  }

  return response.json();
}

The JSON content type and Authorization header can cause a preflight. The Express configuration above allows both. Handle the rejected promise where you call saveProfile; a CORS failure often appears to JavaScript as a network error rather than as a readable API response.

If the request uses cookies

For cross-origin cookies, set credentials: "include" in fetch and enable credentials: true in Express's CORS configuration:

const response = await fetch("http://localhost:4000/api/profile", {
  credentials: "include",
});

Credentialed responses must specify the requesting origin, not Access-Control-Allow-Origin: *. The CORS middleware uses the configured origin to produce the appropriate response header. CORS permission alone does not guarantee that a cookie will be sent: cookie SameSite and Secure attributes, browser cookie policies, and the cookie's domain and path also matter.

If a request does not need cookies or other credentials, leave credentials out of fetch and do not enable credentialed CORS unnecessarily. Never combine credentialed requests with a wildcard allowed origin.

Debug the response headers

For a browser request, check the API response—not just the request code. For preflight, inspect the OPTIONS response for the relevant permissions:

  • Access-Control-Allow-Origin must match the page's origin.
  • Access-Control-Allow-Methods must include the intended method.
  • Access-Control-Allow-Headers must permit the headers the browser requested.
  • Credentialed requests also require Access-Control-Allow-Credentials: true and a specific allowed origin.

If the actual API response is an error, it also needs appropriate CORS headers for browser JavaScript to read that error. Check the Express logs and response status: a server error, missing route, or failed authentication is a separate problem that CORS configuration does not fix.

If the request succeeds from a server-side Next.js function but fails in the browser, that difference is a useful clue: the browser is enforcing CORS on the client-side request. Conversely, adding CORS headers cannot make a server-side request reach an unreachable host or authenticate it.

Common Mistakes

  • Allowing the wrong origin: localhost on port 3000 is not the same origin as localhost on port 4000.
  • Adding CORS headers only to the actual route: the browser may need permission for its OPTIONS preflight first.
  • Forgetting a custom header: allow Authorization or other request headers when the browser asks for them.
  • Using * with credentials: credentialed CORS requires a specific origin.
  • Trying to fix CORS in fetch: request options cannot grant the browser permission that the API response must provide.
  • Treating CORS as access control: non-browser clients can call an API without browser CORS checks. Enforce authentication and authorization independently.

Best Practices

  • Allow only the origins your application needs, especially in production.
  • Keep the CORS middleware before routes and verify preflight behavior in the browser's Network panel.
  • Avoid enabling credentials unless the browser request actually needs them.
  • Configure authentication, authorization, and, where relevant, CSRF defenses separately from CORS.
  • For server-side Next.js requests, use a server-reachable API URL and protect any credentials; do not assume browser CORS rules apply to that request.

Conclusion

To fix a CORS error in a Next.js and Express application, identify whether the call runs in the browser, then configure Express to allow the browser app's exact origin, required method, and request headers. If cookies are involved, enable credentials on both sides and use a specific origin. Check preflight and actual responses separately; CORS is a browser permission mechanism, not a substitute for API security.

References