Handling Common CORS Errors in Next.js 15

Handling Common CORS Errors in Next.js 15

If you've ever found yourself staring at the dreaded "Access to fetch at... has been blocked by CORS policy" error message while developing a Next.js application, you're not alone. As one frustrated developer put it on Reddit, they've "ran into 9 million bugs regarding CORS" while trying to create APIs.

CORS (Cross-Origin Resource Sharing) errors are among the most common and frustrating issues developers face, especially during rapid development phases. While CORS is essential for web security, it can feel like an unnecessary roadblock when you're trying to get your application up and running.

In this comprehensive guide, we'll dive deep into understanding and solving common CORS errors in Next.js 15, with practical solutions that you can implement right away.

Understanding CORS: Why It Exists and Why It's Important

Before we jump into solutions, it's crucial to understand what CORS is and why it matters. CORS is a security mechanism built into web browsers that restricts web applications from making requests to a different domain than the one serving the web page.

For example, if your frontend is running on http://localhost:3000 and it tries to make an API request to https://api.payment/session, the browser will block this request unless the server explicitly allows it through CORS headers.

This security measure exists to protect users from malicious websites that might try to access sensitive information from other domains without permission. As developers note, "CORS won't let you make requests from a browser to a domain that differs from the origination domain" - and for good reason!

Common CORS Headers You Need to Know

Before diving into specific errors, let's understand the essential CORS headers that control cross-origin access:

  1. Access-Control-Allow-Origin: Specifies which origins can access the resource

    • Example: Access-Control-Allow-Origin: * (allows all origins)

    • Example: Access-Control-Allow-Origin: https://yourdomain.com (allows specific origin)

  2. Access-Control-Allow-Methods: Defines allowed HTTP methods

    • Example: Access-Control-Allow-Methods: GET, POST, PUT, DELETE

  3. Access-Control-Allow-Headers: Lists allowed request headers

    • Example: Access-Control-Allow-Headers: Content-Type, Authorization

  4. Access-Control-Allow-Credentials: Indicates if requests can include credentials

    • Example: Access-Control-Allow-Credentials: true

Common CORS Errors and Their Solutions

1. Missing Access-Control-Allow-Origin Header

This is perhaps the most common CORS error you'll encounter in Next.js:

Access to fetch at 'https://api.payment/session' from origin 'http://localhost:3000' 
has been blocked by CORS policy: Response to preflight request doesn't pass access 
control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Solution:

Add the necessary CORS headers in your Next.js API routes. Here's how to do it:

// pages/api/your-endpoint.js
export default function handler(req, res) {
  // Set CORS headers
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');

  // Handle the actual request
  if (req.method === 'OPTIONS') {
    res.status(200).end();
    return;
  }

  // Your API logic here
}

2. Preflight Request Issues

Many developers report issues with preflight requests failing. This happens when the browser sends an OPTIONS request before the actual request, and the server doesn't handle it correctly.

Solution:

Create a middleware to handle CORS consistently across all your API routes:

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

export function middleware(request: NextRequest) {
  const response = NextResponse.next();

  // Add CORS headers
  response.headers.set('Access-Control-Allow-Origin', '*');
  response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');

  return response;
}

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

3. Credentials Issues with Wildcard Origin

A common pitfall occurs when trying to use credentials with a wildcard origin. The browser won't allow requests with credentials when Access-Control-Allow-Origin is set to *.

Solution:

Specify the exact origin instead of using a wildcard:

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: "/api/:path*",
        headers: [
          {
            key: "Access-Control-Allow-Origin",
            value: process.env.ALLOWED_ORIGIN || "http://localhost:3000",
          },
          {
            key: "Access-Control-Allow-Credentials",
            value: "true",
          },
          {
            key: "Access-Control-Allow-Methods",
            value: "GET,OPTIONS,PATCH,DELETE,POST,PUT",
          },
        ],
      },
    ];
  },
};

4. CORS Issues with External APIs

Many developers struggle with CORS when trying to access external APIs from their Next.js applications. This is particularly common during local development.

Solution:

Use API routes as a proxy to make requests to external APIs:

// pages/api/proxy/[...path].js
export default async function handler(req, res) {
  const { path } = req.query;
  const apiUrl = `https://external-api.com/${path.join('/')}`;

  try {
    const response = await fetch(apiUrl, {
      method: req.method,
      headers: {
        'Content-Type': 'application/json',
        // Forward any necessary headers
        ...req.headers,
      },
      body: req.method !== 'GET' ? JSON.stringify(req.body) : undefined,
    });

    const data = await response.json();
    res.status(response.status).json(data);
  } catch (error) {
    res.status(500).json({ error: 'Error proxying request' });
  }
}

5. Testing and Debugging CORS Issues

One frustration developers often face is that headers work in Postman but not in the browser. This is because Postman doesn't enforce CORS policies like browsers do.

Debugging Tips:

  1. Use the browser's developer tools Network tab to inspect the actual requests and responses

  2. Check if preflight OPTIONS requests are being handled correctly

  3. Verify all required CORS headers are present in the response

  4. Test with a simple curl command to isolate browser-specific issues:

curl -X OPTIONS -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: POST" \
  https://your-api.com/endpoint -v

Best Practices and Security Considerations

When implementing CORS in your Next.js application, keep these best practices in mind:

  1. Never Use Wildcards in Production Instead of using Access-Control-Allow-Origin: *, explicitly list allowed origins:

    const allowedOrigins = ['https://app.yourdomain.com', 'https://admin.yourdomain.com'];
    const origin = req.headers.origin;
    if (allowedOrigins.includes(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
    }
  2. Implement Proper Error Handling Always return appropriate error responses when CORS checks fail:

    if (!allowedOrigins.includes(origin)) {
      return res.status(403).json({ 
        error: 'Origin not allowed' 
      });
    }
  3. Use Environment Variables Store allowed origins in environment variables for different environments:

    const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',');
  4. Regular Auditing Regularly review and update your CORS policies to ensure they align with your security requirements.

Conclusion

CORS errors in Next.js can be frustrating, but they're not insurmountable. By understanding why CORS exists and implementing the appropriate solutions, you can effectively manage cross-origin requests while maintaining security.

Remember that while it might be tempting to use broad CORS permissions during development, it's crucial to implement proper restrictions in production. As one developer wisely noted, "The annoying part is local development, in a rush" - but taking the time to implement CORS correctly will save you from security headaches down the road.

By following the solutions and best practices outlined in this guide, you'll be well-equipped to handle CORS issues in your Next.js applications effectively and securely.

Raymond Yeh

Raymond Yeh

Published on 14 November 2024

Get engineers' time back from marketing!

Don't let managing a blog on your site get in the way of your core product.

Wisp empowers your marketing team to create and manage content on your website without consuming more engineering hours.

Get started in few lines of codes.

Choosing a CMS
Related Posts
Next.js 14 App Router: GET & POST Examples (with TypeScript)

Next.js 14 App Router: GET & POST Examples (with TypeScript)

Ready to master Next.js 14's App Router? Learn to create GET & POST route handlers with ease. Discover practical uses and advanced features using TypeScript. Start coding smarter today!

Read Full Story
API in Next.js 15: GET & POST Route Handler Examples

API in Next.js 15: GET & POST Route Handler Examples

Build an API Endpoint in Next.js 15 using the latest route handler. See what's new and get code example to serve your API.

Read Full Story
Mental Model for Client/Server Components, Static/Dynamic Route & Caching in Next.js

Mental Model for Client/Server Components, Static/Dynamic Route & Caching in Next.js

Confused about Server Component vs Client Component, Dynamic vs Static Routes & Caching in Next.js? You are not alone. Use this mental model to help you!

Read Full Story