Antonello Zanini I'm a software engineer, but I prefer to call myself a technology bishop. Spreading knowledge through writing is my mission.

Using CORS in Next.js to handle cross-origin requests

7 min read 2119 109

Using Cors In Next Js To Handle Cross Origin Requests

Over the past few years, Next.js has become a go-to framework not only for frontend development, but also for robust API development. When building APIs, it is critical to spend some time on Cross-Origin Resource Sharing (CORS).

In this complete guide, you will explore several methods for using CORS in Next.js, enabling you to handle cross-origin requests effectively and securely.

Jump ahead:

What is CORS?

CORS is a security mechanism that enables a server to specify which origins are allowed to access and load resources in a web browser. In this context, an “origin” refers to the combination of the protocol, domain, and port number a request comes from.

In detail, CORS is a protection system implemented by web browsers to enforce restrictions on cross-origin requests. Its main goal is to protect user security and prevent unauthorized access to resources and data.

To enforce CORS, browsers and servers exchange a set of specific HTTP headers.

Before making certain types of cross-origin requests, the browser sends a preflight request using the HTTP OPTIONS method to the server. The server then responds with some CORS headers, indicating whether the cross-origin should be permitted or denied.

If the preflight request is successful, the actual cross-origin request is performed by the browser. Otherwise, it gets blocked because of a CORS policy error.

The main CORS headers include:

  • Access-Control-Allow-Origin — Specifies the origin that has access to the resource
  • Access-Control-Allow-Methods — Added to the preflight response to indicate the permitted HTTP methods, such as GET, POST, PUT, etc.
  • Access-Control-Allow-Headers — Returned in response to a preflight request to specify the HTTP headers that are allowed in the current request
  • Access-Control-Allow-Credentials — Indicates whether the browser should include credentials such as cookies or HTTP authentication in the cross-origin request

CORS allows frontend applications to access resources from a different domain, ensuring secure cross-origin communication. By default, Next.js relies on a same-origin approach, imposing a strict policy. If you want to change that, you must configure it manually.

Why you need CORS with Next.js

Next.js has supported API development since v9.4. As mentioned earlier, APIs in Next.js do not involve CORS headers by default, following a same-origin policy instead. This means they can only be accessed in the browser by pages within the same domain.

However, there are scenarios where you may want to access those endpoints from other origins. For example, if you had an application hosted on a different domain that needed to access those APIs in the frontend, it would be blocked by those cross-origin restrictions.

To avoid that issue, you need to enable CORS by configuring the appropriate headers on the Next.js server. This way, you can explicitly allow cross-origin requests for specific origins. Applications hosted on the trusted domains will then be able to make requests to your Next.js API routes.



Let’s now learn how to configure CORS in Next.js!

Prerequisites for learning to configure CORS in Next.js

Before moving to the next section, be sure to meet the prerequisites below:

  • Node.js v18+ installed on your machine
  • A Next.js v13+ app

If your Next.js application is older than the recommended version, you can upgrade it by following the official migration guide.

To follow this tutorial, it does not matter if your project is based on the Pages Router or the new App Router. The proposed solutions will work in both scenarios.

From now on, we will assume that all API routes in your Next.js project are under the /api path.

Enabling CORS for all API routes

The most common approach to CORS is to set it up globally on all API endpoints. There are many ways to achieve that in Next.js. Here, you will explore three methods for using CORS in Next.js.

Using the headers config

You can manually set the CORS headers in Next.js thanks to the headers key in next.config.js:

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
    async headers() {
        return [
            {
                // matching all API routes
                source: "/api/:path*",
                headers: [
                    { key: "Access-Control-Allow-Credentials", value: "true" },
                    { key: "Access-Control-Allow-Origin", value: "*" }, // replace this your actual origin
                    { key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT" },
                    { 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" },
                ]
            }
        ]
    }
}

module.exports = nextConfig

headers() is an async function that must return an array of objects. When the incoming request matches the source path regex, the server will produce a response containing the custom HTTP headers.

Note that the source must follow a specific syntax. If an incorrect path regex is used, Next.js will throw an Invalid header found error and fail with a `source` parse failed message.

Using a middleware

Next.js middleware enables you to perform specific operations before a request is completed. This also includes setting the CORS HTTP headers in the response. Based on your project structure, initialize a middleware.js file inside the pages, app, or the src folder as shown below:

// src/middleware.js 
// or 
// src/app/middleware.js 
// or 
// src/pages/middleware.js 

import { NextResponse } from "next/server";

export function middleware() {
    // retrieve the current response
    const res = NextResponse.next()

    // add the CORS headers to the response
    res.headers.append('Access-Control-Allow-Credentials', "true")
    res.headers.append('Access-Control-Allow-Origin', '*') // replace this your actual origin
    res.headers.append('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT')
    res.headers.append(
        'Access-Control-Allow-Headers',
        'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
    )

    return res
}

// specify the path regex to apply the middleware to
export const config = {
    matcher: '/api/:path*',
}

Make sure to adapt the CORS policy specified in this snippet to your needs.

Note that the matcher config accepts a regex path, forcing the middleware to run only on specific paths. If an incorrect value is used, Next.js will fail and display an Invalid middleware found error message.

If your app relies on the App Router, you can remove the config export and place this file directly in the src/app/api path. In both cases, the middleware function will be applied to all API endpoints under the /api path.

Using a Vercel configuration file

Chances are good that your Next.js project is hosted on Vercel, since Vercel is the development team behind Next.js. You can configure the CORS headers in a Next.js app deployed on Vercel by initializing a vercel.json file in the root folder of your project like so:

{
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Credentials", "value": "true" },
        { "key": "Access-Control-Allow-Origin", "value": "*" },
        { "key": "Access-Control-Allow-Methods", "value": "GET,DELETE,PATCH,POST,PUT" },
        { "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" }
      ]
    }
  ]
}

As you can see, the headers key has a similar syntax to the equivalent one in next.config.js. What changes is the format of the source field.

Alternatively, you can also set up CORS with the following Vercel serverless function:

const enableCors = fn => async (req, res) => {
  res.setHeader('Access-Control-Allow-Credentials', true)
  res.setHeader('Access-Control-Allow-Origin', '*') // replace this your actual origin
  res.setHeader('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT')
  res.setHeader(
    'Access-Control-Allow-Headers',
    'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
  )

  // specific logic for the preflight request
  if (req.method === 'OPTIONS') {
    res.status(200).end()
    return
  }

  return await fn(req, res)
}

const handler = (req, res) => {
  const currentDate = new Date()
  res.end(currentDate.toString())
}

module.exports = enableCors(handler)

Et voilà! You now know how to use CORS on all your Next.js API endpoints!

CORS headers in action

Now, assume your frontend app needs to perform a GET API call to /api/hello-world. This is what the preflight request will look like:

Chrome Devtools In Dark Mode Open To Network Tab Displaying Preflight Request To Perform Get Api Call To Api Hello World

By the 2xx HTTP status code, you can tell that the preflight request was performed successfully. The browser is now allowed to execute the actual cross-origin request:

Chrome Devtools In Dark Mode Open To Network Tab Displaying Browser Executing Cross Origin Request After Successful Preflight Request

Focus on the CORS headers in the “Response Headers” section. They match the ones defined in the approaches above.

General tips for using CORS in Next.js

Before wrapping up, there are some CORS Next.js tips and tricks you should know.

Reading the ALLOW header values from the env variables

CORS policies may change over time. Thus, you should not hard-code them. Instead, you can set up some environment variables as shown below:

ACCESS_CONTROL_ALLOW_CREDENTIALS="true"
ACCESS_CONTROL_ALLOW_ORIGIN="*"
ACCESS_CONTROL_ALLOW_METHODS="GET,OPTIONS,PATCH,DELETE,POST,PUT"
ACCESS_CONTROL_ALLOW_HEADERS="X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version"

Then, use them in your code:

res.headers.append('Access-Control-Allow-Credentials', process.env.ACCESS_CONTROL_ALLOW_CREDENTIALS)
res.headers.append('Access-Control-Allow-Origin', process.env.ACCESS_CONTROL_ALLOW_ORIGIN) // replace this your actual origin
res.headers.append('Access-Control-Allow-Methods', process.env.ACCESS_CONTROL_ALLOW_METHODS)
res.headers.append('Access-Control-Allow-Headers', process.env.ACCESS_CONTROL_ALLOW_HEADERS)

Great! The CORS definition logic no longer involves hard-coded strings.

Allowing multiple CORS origins

The Access-Control-Allow-Origin header is a binary option that accepts either a single origin or all origins. You would use an asterisk * to set this header to accept all domains, but this wildcard cannot be used when credentials are included in the request.

If you want to permit multiple but not all CORS origins, you need to write custom logic. Keep in mind that you can only implement this approach with the middleware solution. The other two options for enabling CORS in Next.js involve static files that do not accept custom behavior.

Update the middleware.js file as below:

import { NextResponse } from "next/server";

// the list of all allowed origins
const allowedOrigins = [
  'http://localhost:3000', 
  'https://example-1.com', 
  'https://example-2.com', 
  // ...
  'https://example-99.com', 
];

export function middleware(req) {
    // retrieve the current response
    const res = NextResponse.next()

    // retrieve the HTTP "Origin" header 
    // from the incoming request
    req.headers.get("origin")

    // if the origin is an allowed one,
    // add it to the 'Access-Control-Allow-Origin' header
    if (allowedOrigins.includes(origin)) {
      res.headers.append('Access-Control-Allow-Origin', origin);
    }

    // add the remaining CORS headers to the response
    res.headers.append('Access-Control-Allow-Credentials', "true")
    res.headers.append('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT')
    res.headers.append(
        'Access-Control-Allow-Headers',
        'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
    )

    return res
}

// specify the path regex to apply the middleware to
export const config = {
    matcher: '/api/:path*',
}

Basically, all you have to do is check whether the current Origin header sent by the browser matches one of those allowed. If so, specify the Origin variable in the appropriate CORS header to enable cross-origin requests from it.

Enabling CORS only for a specific endpoint

Suppose you want to define a particular CORS policy for the /api/special-data endpoint. To do so, you could add a new entry to the array returned by headers() in next.config.js:

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
    async headers() {
        return [
            {
                // matching all API routes
                source: "/api/:path*",
                headers: [
                   // omitted for brevity...
                ]
            },
            {
                source: "/api/special-data",
                headers: [
                    { key: "Access-Control-Allow-Credentials", value: "false" },
                    { key: "Access-Control-Allow-Origin", value: "https://example.com" },
                    { key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT" },
                    { key: "Access-Control-Allow-Headers", value: "Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date" },
                ]
            }
        ]
    }
}

module.exports = nextConfig

Note that the source field of the second entry matches the endpoint of the desired API. This means that Next.js will prefer it to the more generic first entry.

Otherwise, you could update middleware.js and adapt its logic based on the request’s pathname:

import { NextResponse } from "next/server";

export function middleware(req) {
    // retrieve the current response
    const res = NextResponse.next()

    // if the incoming is for the desired API endpoint
    if (req.nextUrl.pathname === '/special-data') {
        res.headers.append('Access-Control-Allow-Credentials', "false")
        res.headers.append('Access-Control-Allow-Origin', 'https://example.com') 
        res.headers.append('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT')
        res.headers.append('Access-Control-Allow-Headers', 'Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date')
    } else {
        // generic CORS policy omitted for brevity....
    }

    return res
}

// specify the path regex to apply the middleware to
export const config = {
    matcher: '/api/:path*',
}

If you are an App Router user, you can get the same result by creating a middleware.js file in the src/app/special-data folder.

Fantastic! The single-origin restriction is no longer an issue!

Conclusion

In this article, you learned what CORS is and why it is so important to use it in backend applications. More specifically, we took a look at the most critical CORS headers and how to set them in Next.js.

As you saw here, there are several ways to deal with CORS in Next.js. They all just involve some lines of code, so it does not take much to put a security policy in place.

Thanks to the Next.js capabilities, configuring CORS has never been easier. Make your site more secure today!

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket. LogRocket Dashboard Free Trial Banner

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — start monitoring for free.

Antonello Zanini I'm a software engineer, but I prefer to call myself a technology bishop. Spreading knowledge through writing is my mission.

Leave a Reply