Next.js API Routes CORS 에러 완벽 해결: 미들웨어(Middleware) 아키텍처

[Troubleshooting] Next.js API Routes CORS 에러 완벽 해결: 미들웨어 아키텍처

"프론트엔드와 백엔드를 한 번에 처리하려고 Next.js의 app/api (또는 pages/api) 라우터를 구축했습니다. 그런데 외부 도메인에 있는 클라이언트가 이 API를 호출하니 브라우저 콘솔이 Blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present 에러로 피바다가 됩니다."

서버리스(Serverless) API 트렌드에 맞춰 Next.js 풀스택 아키텍처를 시도하는 프론트엔드 개발자들이 가장 크게 겪는 좌절입니다. Express.js나 Spring Boot를 썼을 때는 라이브러리 한 줄로 해결되던 CORS 설정이, Next.js 환경에서는 당최 어디에 집어넣어야 할지 감이 잡히지 않기 때문입니다.

초보자들은 API 라우터 함수마다 일일이 res.setHeader()를 도배하는 최악의 중복 코드를 작성합니다. 본 가이드에서는 브라우저의 Preflight(사전 요청) 보안 매커니즘을 해체하고, 미들웨어(Middleware)와 환경 설정을 통해 모든 API 통로에 방어막을 쳐주는 글로벌 CORS 아키텍처를 단호하게 제시합니다.

📌 이 글의 핵심 포인트

  • CORS의 본질: 서버의 잘못이 아니라 클라이언트(브라우저)가 교차 출처 리소스를 차단하는 방어 기제
  • Preflight OPTIONS: 브라우저가 본 요청(POST)을 보내기 전 서버의 허락을 구하는 핑(Ping) 테스트
  • 글로벌 방어망: next.config.js의 headers 옵션을 활용한 전역적인 보안 헤더 주입 아키텍처

1단계: 에러의 본질 - OPTIONS 찔러보기

당신의 도메인이 front.com이고 Next.js API가 api.com에 있다면, 브라우저는 POST 방식의 무거운 데이터를 쏘기 전에 "너네 서버 내가 접근해도 돼?"라고 묻는 가벼운 OPTIONS 메소드 요청(Preflight)을 몰래 던져봅니다.

이때 Next.js API 서버가 "응, front.com에서 온 요청은 허락할게 (Access-Control-Allow-Origin)"라는 헤더(Header)를 담아주지 않으면, 브라우저는 본 요청을 아예 취소해 버리고 CORS 에러를 뿜어냅니다.

2단계: 완벽한 트러블슈팅 - next.config.js 전역 설정

모든 API 라우터마다 헤더를 수동으로 삽입하는 미련한 짓은 멈춰야 합니다. Next.js 프로젝트 최상단에 있는 next.config.js 파일에서 headers() 설정을 오버라이드하면, 특정 경로(예: /api/*)로 들어오는 모든 요청에 자동으로 CORS 통행증을 발급할 수 있습니다.


/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        // /api/ 하위의 모든 라우터에 CORS 헤더를 강제 주입
        source: "/api/:path*",
        headers: [
          { key: "Access-Control-Allow-Credentials", value: "true" },
          { key: "Access-Control-Allow-Origin", value: "https://www.허락할도메인.com" }, // 와일드카드(*)는 권장하지 않음
          { key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,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;

위 설정을 적용하고 서버를 재시작하면, 브라우저의 깐깐한 Preflight 요청이 통과되며 CORS 에러가 완벽히 소멸합니다.

3단계: 동적(Dynamic) 도메인 허용 - Middleware 도입

만약 허락해야 할 도메인이 수십 개라서 정적인 next.config.js로 커버하기 어렵다면, middleware.js(또는 .ts)를 최상단에 생성하여 요청이 들어올 때마다 동적으로 헤더를 조작하는 엔터프라이즈 아키텍처를 도입하십시오.


import { NextResponse } from 'next/server';

export function middleware(request) {
    const origin = request.headers.get('origin');
    // 허용된 화이트리스트 도메인 배열
    const allowedOrigins = ['https://a.com', 'https://b.com'];

    const response = NextResponse.next();

    // 들어온 origin이 화이트리스트에 있다면 동적으로 헤더 세팅
    if (allowedOrigins.includes(origin)) {
        response.headers.set('Access-Control-Allow-Origin', origin);
        response.headers.set('Access-Control-Allow-Credentials', 'true');
        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*',
};

🙋‍♂️ 자주 묻는 질문 (FAQ)

Q. Access-Control-Allow-Origin 값에 *(별표)를 넣으면 안 되나요?
A. 퍼블릭 오픈 API라면 상관없지만, 사용자 인증이 필요한 쿠키(Credentials)를 주고받는 환경에서는 *를 쓰면 브라우저가 보안 위험으로 간주하고 다시 차단해 버립니다. 반드시 명확한 도메인 주소를 적어주어야 합니다.

💡 핵심 정리 및 마무리

CORS 정책은 개발자를 괴롭히기 위한 에러가 아니라, 악의적인 해커로부터 사용자의 브라우저를 보호하는 거대한 방패입니다. 억지로 보안을 우회하려 하지 말고, 프레임워크가 제공하는 글로벌 헤더(Header) 주입 시스템을 통제하여 안전하고 유연한 풀스택 API 인프라를 완성하십시오.

댓글

이 블로그의 인기 게시물

Zapier & Make.com 자동화의 덫: 무한 루프(Infinite Loop) 에러 완벽 방어 아키텍처

엑셀 보고서의 종말: 구글 스프레드시트와 루커 스튜디오(Looker Studio)로 실시간 대시보드 구축하기

Docker OOMKilled (Exit Code 137) 에러의 진실: 컨테이너 메모 누수 방어 및 리소스 최적화 아키텍처