Avoiding CORS Issues in React/Next.js

Avoiding CORS Issues in React/Next.js

If you’ve written any frontend code, you’ve likely encountered a CORS error before, like this:

Access to _ has been blocked by CORS policy

Cross-Origin Resource Sharing (CORS) is a protocol that defines how web requests should be handled when they are across different URLs.

Why are cross origin requests an issue?

Your browser holds a lot of state about you for every website you visit. Let’s say you are logged in to mybank.com and you have a cookie set with mybank.com indicating that you are logged in.

While you are browsing, you accidentally visit malicious.site, which makes a request to mybank.com that looks like this:

// Fetch request made from https://malicious.site
fetch("https://api.mybank.com/account_details", {
    method: "GET",
    credentials: "include",
})

If this request was allowed and your cookie was included, the owner of malicious.site would be able to make requests on your behalf and read your account details.

With CORS, the server is allowed to specify which cross-origin requests it will accept vs reject. It can reject requests that need cookies. It can reject requests from untrusted.mybank.com but accept requests from app.mybank.com. It can reject all POST requests but allow GETs and PUTs.

The important thing to note about CORS is that the configuration/settings are done on the server and enforced by both the server and your browser. Most server frameworks have a library for configuring your CORS headers, but if you want to see the underlying headers themselves, here’s a good resource.

Practical example

Let’s say we have a React application with an Express backend. Our frontend is running on port 3000 - a common default for React.

Since our backend cannot also run on port 3000 locally, we’ll setup Express to run on port 4000.

const express = require('express')
const app = express()
const port = 4000

app.get('/whoami', (req, res) => {
    res.send('Who is anybody?')
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

If our React application made a fetch request to our backend like this:

// Fetch request made from http://127.0.0.1:3000
fetch("http://127.0.0.1:4000/whoami")

we should expect a CORS error. Just like our earlier example, 127.0.0.1:3000 and 127.0.0.1:4000 are treated as two separate domains, so you cannot make requests across them yet. Let’s look at a few ways to fix this.

Fix 1: Fixing the server

One approach is to fix our server. We can do this by installing a CORS library (https://www.npmjs.com/package/cors) and telling the server to expect requests from 127.0.0.1:3000

app.use(cors({
    origin: 'http://127.0.0.1:3000',
}))

Our requests will now succeed. This approach is pretty straightforward and is generally good practice. We can use this same approach in production in case our frontend and backend are hosted on two different subdomains.

Fix 2: Adding a proxy

In production, in some cases, you’ll host your frontend and backend from the same origin. In these cases, you usually want to write fetch code that looks like this:

fetch("/whoami")

instead of like this:

const url;
if (process.env.NODE_ENV === "production") {
    url = "https://www.example.com/whoami"
} else {
    url = "http://127.0.0.1:4000/whoami"
}
fetch(url)

To do this, create-react-app actually comes natively with the ability to set up a simple proxy. Add the following to your package.json:

"proxy": "http://localhost:4000",

Then, any request that looks like an API request (e.g. has a content type application/json) will automatically get forwarded to http://localhost:4000.

The reason this bypasses CORS issues is because, to the browser, the request is going to http://localhost:3000 and therefore doesn’t look like a cross-origin request. The server at http://localhost:3000 does forward the request to http://localhost:4000, but the browser doesn’t know that.

If you are using Next.js instead of create-react-app, you can set up a redirect in your next.config.js which will forward all matching traffic:

module.exports = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'http://localhost:4000/:path*'
      }
    ]
  }
}

Summary

The simplest way to fix any CORS issues in React and Next.js is actually not to change anything in React or Next.js but instead to fix your server to allow requests from them.

If you cannot change the server, both React and Next.js have ways for you to turn them into proxies and proxy requests to your backend on your behalf. This avoids any CORS issues by making your browser think the request is not a cross-origin request anymore.