본 사이트는 파트너스 활동으로 수수료를 받으며, 서버 운영과 무료 앱 개발에 사용됩니다. 본 사이트는 파트너스 활동으로 수수료를 받으며
서버 운영과 무료 앱 개발에 사용됩니다.
목록으로
바이브 코딩

R2 CORS 설정 | Vercel 서버리스 우회 업로드와 바이브 코딩 시 반드시 이해해야 하는 개념

최초 발행: 2026년 3월 20일 오후 02:28 | 최종 수정: 2026년 3월 20일 오후 02:29

브라우저에서 파일을 업로드하거나 외부 리소스를 불러올 때 콘솔에 붉은 글씨로 Access to fetch has been blocked by CORS policy라는 에러가 뜨는 경험은 웹 개발자라면 누구나 한 번쯤 겪는다. 특히 바이브 코딩으로 빠르게 프로젝트를 만들 때, 로컬 개발 환경에서는 문제없이 작동하던 기능이 배포 직후 CORS 에러로 멈추는 상황은 매우 흔하다.

CORS(Cross-Origin Resource Sharing)는 단순한 설정값이 아니다. 브라우저가 사용자를 보호하기 위해 30년 가까이 유지해 온 동일 출처 정책(Same-Origin Policy) 위에 세워진 보안 메커니즘이며, 이 구조를 이해하지 못하면 Cloudflare R2 직접 업로드, Vercel 서버리스 payload 우회 같은 실전 아키텍처를 제대로 구성할 수 없다.

이 문서에서는 CORS가 탄생한 배경부터, R2 버킷에 CORS를 설정하는 구체적 방법, Vercel 서버리스 4.5MB 제한을 presigned URL로 우회할 때 사이트 주소와 localhost를 AllowedOrigins에 등록해야 하는 원리까지 하나의 흐름으로 정리한다.

1. CORS의 탄생 배경과 동일 출처 정책

1.1. 동일 출처 정책이 만들어진 이유

  1. 1995년 Netscape Navigator 2.0에 JavaScript가 처음 탑재되면서, 스크립트가 브라우저 안에서 다른 웹페이지의 데이터에 접근할 수 있는 가능성이 열렸다. 예를 들어 악의적인 사이트 A가 사용자의 브라우저를 통해 은행 사이트 B의 계좌 정보를 읽어올 수 있다면 심각한 보안 문제가 된다.
  2. 이 위험을 차단하기 위해 Netscape는 동일 출처 정책(Same-Origin Policy, SOP) 을 도입했다. 핵심 원칙은 간단한데, 한 출처(Origin)에서 로드된 스크립트는 다른 출처의 리소스에 자유롭게 접근할 수 없다는 것이다.
  3. 여기서 출처(Origin) 는 세 가지 요소의 조합으로 정의된다. 바로 스킴(프로토콜), 호스트(도메인), 포트 세 가지다. https://example.com:443http://example.com:80은 스킴이 다르므로 서로 다른 출처이고, https://example.comhttps://api.example.com은 호스트가 다르므로 역시 다른 출처다.
  4. SOP는 지금도 모든 주요 브라우저(Chrome, Firefox, Safari, Edge)에서 기본 보안 정책으로 작동하고 있으며, 이 정책이 없으면 웹은 사실상 안전하지 않다.

1.2. 그런데 왜 다른 출처 접근이 필요해졌는가

  1. 웹이 발전하면서 프론트엔드와 백엔드가 분리되고, CDN에서 이미지를 로드하고, 외부 API를 호출하는 구조가 보편화되었다. https://myapp.com에서 https://api.myapp.com이나 https://cdn.example.com의 리소스를 불러와야 하는 상황이 일상이 된 것이다.
  2. SOP만 존재하면 이런 정당한 교차 출처 요청까지 전부 차단된다. 이 문제를 해결하기 위해 2006년경부터 W3C에서 표준화 작업이 시작되었고, 그 결과물이 바로 CORS(Cross-Origin Resource Sharing) 다.
  3. CORS는 SOP를 대체하는 것이 아니라, SOP 위에서 서버가 명시적으로 허용한 출처에 한해 교차 출처 접근을 가능하게 하는 확장 메커니즘이다. 서버가 HTTP 응답 헤더를 통해 허용 여부를 브라우저에 알려주는 구조이므로, 허용 권한은 항상 서버 쪽에 있다.
핵심 포인트: CORS는 브라우저의 보안 정책(SOP)을 완화하는 메커니즘이다. 서버가 응답 헤더로 특정 출처를 허용하지 않으면 브라우저는 응답 데이터를 JavaScript에 전달하지 않는다. 이 구조는 서버가 아닌 브라우저가 강제하는 것이며, curl이나 Postman 같은 도구에서는 CORS가 적용되지 않는다.

2. CORS의 작동 원리와 핵심 헤더

2.1. 단순 요청과 사전 요청(Preflight)

  1. 브라우저가 교차 출처 요청을 보낼 때, 요청의 종류에 따라 두 가지 방식으로 처리된다. 첫 번째는 단순 요청(Simple Request) 으로, GET이나 POST 메서드를 사용하고 특별한 커스텀 헤더가 없는 경우다. 이때 브라우저는 요청을 바로 보내고, 서버 응답의 Access-Control-Allow-Origin 헤더를 확인해서 JavaScript의 응답 접근 허용 여부를 결정한다.
  2. 두 번째는 사전 요청(Preflight Request) 으로, PUT, DELETE 같은 메서드를 사용하거나, Content-Type: application/json 같은 비표준 헤더를 포함하는 경우에 발생한다. 브라우저는 실제 요청을 보내기 전에 OPTIONS 메서드로 서버에 먼저 물어본다. 이 서버에 PUT 요청을 보내도 되는지, 이 헤더를 포함해도 되는지 확인하는 것이다.
  3. 서버가 OPTIONS 요청에 대해 Access-Control-Allow-Methods: PUTAccess-Control-Allow-Headers: Content-Type 같은 헤더를 응답으로 돌려주면, 브라우저는 비로소 실제 PUT 요청을 전송한다. 사전 요청이 실패하면 실제 요청은 아예 전송되지 않는다.

2.2. CORS 응답 헤더 정리

헤더 이름역할예시 값
Access-Control-Allow-Origin허용할 출처 지정https://myapp.com 또는 *
Access-Control-Allow-Methods허용할 HTTP 메서드GET, PUT, POST, DELETE
Access-Control-Allow-Headers허용할 요청 헤더Content-Type, Authorization
Access-Control-Expose-HeadersJavaScript에서 읽을 수 있는 응답 헤더ETag, Content-Length
Access-Control-Max-AgePreflight 응답 캐시 시간(초)3600
Access-Control-Allow-Credentials쿠키·인증 정보 포함 허용true

이 중에서 가장 중요한 것은 Access-Control-Allow-Origin이다. 이 헤더가 요청을 보낸 출처와 일치하지 않으면, 나머지 헤더가 아무리 올바르게 설정되어 있어도 브라우저는 응답을 차단한다.

2.3. 와일드카드(*)와 특정 출처 지정의 차이

  1. Access-Control-Allow-Origin: *는 모든 출처를 허용한다는 의미다. 공개적으로 읽기만 가능한 CDN 이미지나 공용 API에는 적합하지만, 인증 정보(쿠키, Authorization 헤더)를 포함하는 요청에서는 사용할 수 없다. 브라우저가 와일드카드와 Access-Control-Allow-Credentials: true를 동시에 허용하지 않기 때문이다.
  2. 파일 업로드처럼 특정 사이트에서만 접근해야 하는 경우, https://myapp.vercel.app이나 http://localhost:3000처럼 정확한 출처를 지정해야 한다. 이렇게 하면 허가받지 않은 도메인에서의 요청을 차단할 수 있어 보안성이 높아진다.
  3. OWASP에서도 민감한 데이터를 다루는 엔드포인트에서는 와일드카드 대신 화이트리스트 방식으로 출처를 명시하도록 권장하고 있다.
핵심 포인트: Preflight 요청은 브라우저가 실제 요청 전에 서버에 허가를 구하는 절차다. PUT이나 커스텀 헤더를 사용하는 업로드 요청은 반드시 Preflight를 거치며, 서버의 CORS 설정에 해당 메서드와 헤더가 포함되어 있어야 한다.

3. Vercel 서버리스 payload 제한과 R2 직접 업로드 구조

3.1. Vercel 서버리스 함수의 4.5MB 벽

  1. Vercel에 배포한 Next.js나 기타 프레임워크의 API 라우트는 서버리스 함수(Serverless Function)로 실행된다. 이 서버리스 함수에는 요청 본문(request body) 크기 4.5MB 라는 하드 제한이 존재한다. 이 제한을 초과하면 HTTP 413 FUNCTIONPAYLOADTOO_LARGE 에러가 발생한다.
  2. 로컬 개발 환경에서는 이 제한이 적용되지 않기 때문에, 10MB 이미지를 업로드해도 잘 작동한다. 그런데 Vercel에 배포하면 갑자기 업로드가 실패하는 상황이 벌어진다. 바이브 코딩 시 AI가 로컬에서 테스트한 코드를 그대로 배포하면 높은 확률로 만나게 되는 함정이다.
  3. 이 제한은 Vercel 인프라의 구조적 특성에서 온 것이며, Pro 플랜으로 업그레이드해도 변경되지 않는다. Vercel 공식 문서에서도 대용량 파일은 서버리스 함수를 거치지 않고 스토리지에 직접 업로드하라고 안내하고 있다.

3.2. Presigned URL을 이용한 직접 업로드 패턴

  1. 해결 방법의 핵심은 클라이언트(브라우저)가 Vercel 서버리스 함수를 경유하지 않고, R2 버킷에 직접 파일을 업로드하는 것이다. 이때 사용하는 것이 Presigned URL(사전 서명된 URL) 이다.
  2. 전체 흐름은 다음과 같다. 먼저 클라이언트가 서버리스 함수에 presigned URL 생성을 요청한다. 이 요청에는 파일 본문이 포함되지 않으므로 4.5MB 제한과 무관하다. 서버리스 함수는 R2의 S3 호환 API를 사용해 presigned URL을 생성하고 클라이언트에 반환한다. 클라이언트는 이 URL로 R2 버킷에 직접 PUT 요청을 보내 파일을 업로드한다.
  3. 이 패턴에서 서버리스 함수가 처리하는 데이터는 presigned URL 문자열 하나뿐이므로, 100MB 파일이든 1GB 파일이든 서버리스 함수의 payload 제한과는 완전히 무관하게 업로드가 가능하다.
항목서버 경유 업로드Presigned URL 직접 업로드
파일 경로브라우저 → Vercel 함수 → R2브라우저 → R2 직접
Vercel payload 제한4.5MB 적용적용되지 않음
서버 부하파일 크기만큼 메모리 사용URL 생성 비용만 발생
CORS 설정 필요 여부불필요(같은 출처)필수(교차 출처)
대용량 파일 지원불가(4.5MB 초과 시 실패)R2 단일 객체 최대 5GB 지원
핵심 포인트: Presigned URL 직접 업로드는 Vercel 서버리스 4.5MB 제한을 우회하는 표준 패턴이다. 하지만 브라우저가 R2 버킷 URL로 직접 요청을 보내는 구조이므로, 교차 출처 요청이 되어 R2 버킷에 CORS 설정이 반드시 필요하다.

4. R2 CORS 설정이 필요한 이유와 구체적 방법

4.1. 왜 R2에 CORS를 설정해야 하는가

  1. Presigned URL 자체는 인증과 권한을 URL 쿼리 파라미터에 포함하고 있어서, 누가 이 URL을 가지고 있든 해당 작업(PUT, GET 등)을 수행할 수 있다. 그런데 presigned URL이 유효해도 CORS가 설정되어 있지 않으면 브라우저에서는 업로드가 실패한다.
  2. 이유는 앞서 설명한 브라우저의 SOP 때문이다. https://myapp.vercel.app에서 실행되는 JavaScript가 https://my-bucket.account-id.r2.cloudflarestorage.com으로 PUT 요청을 보내면, 이것은 완전히 다른 출처에 대한 요청이다. 브라우저는 먼저 OPTIONS Preflight 요청을 보내고, R2 서버가 올바른 CORS 헤더를 응답하지 않으면 실제 PUT 요청 자체를 전송하지 않는다.
  3. curl이나 Postman, 또는 서버 사이드 코드에서 presigned URL로 업로드하면 CORS 에러가 발생하지 않는다. CORS는 오직 브라우저에서만 강제되는 정책이기 때문이다. 바이브 코딩 시 서버 코드에서 테스트할 때는 문제가 없다가, 프론트엔드에서 호출하면 갑자기 실패하는 원인이 바로 이것이다.

4.2. R2 CORS 설정 항목별 역할

Cloudflare R2 대시보드에서 버킷의 Settings 탭으로 이동하면 CORS Policy 섹션에서 JSON 형식으로 정책을 입력할 수 있다. 각 항목의 의미는 다음과 같다.

R2 CORS 필드대응하는 응답 헤더설정 시 고려사항
AllowedOriginsAccess-Control-Allow-Origin요청을 보내는 사이트의 출처를 정확히 입력. 경로(/) 포함 불가
AllowedMethodsAccess-Control-Allow-Methods업로드는 PUT, 다운로드는 GET, 삭제는 DELETE 포함
AllowedHeadersAccess-Control-Allow-HeadersContent-Type 필수. 커스텀 헤더 사용 시 해당 헤더명 추가
ExposeHeadersAccess-Control-Expose-HeadersJavaScript에서 ETag 등을 읽으려면 명시 필요
MaxAgeSecondsAccess-Control-Max-AgePreflight 캐시 시간. 3600(1시간) 권장
  1. AllowedOrigins에는 스킴 + 호스트 + 포트까지만 입력한다. https://myapp.vercel.app은 유효하지만, https://myapp.vercel.app/ 또는 https://myapp.vercel.app/upload는 잘못된 형식이다. 경로가 포함되면 R2가 해당 출처를 인식하지 못한다.
  2. AllowedMethods에 PUT을 빠뜨리면 presigned URL로 업로드할 때 Preflight에서 실패한다. 업로드와 다운로드를 모두 처리하려면 GET과 PUT을 함께 포함해야 한다.
  3. MaxAgeSeconds를 설정하면 브라우저가 Preflight 응답을 캐시해서, 같은 출처에서 반복 요청 시 매번 OPTIONS 요청을 보내지 않는다. 네트워크 비용과 응답 지연을 줄일 수 있으며, 최대값은 86400(24시간)이지만 브라우저에 따라 2시간으로 제한되기도 한다.

5. 사이트 주소와 localhost를 모두 등록해야 하는 이유

5.1. 개발 환경과 프로덕션 환경은 서로 다른 출처

  1. 로컬에서 Next.js 개발 서버를 실행하면 주소는 보통 http://localhost:3000이다. 배포된 사이트 주소는 https://myapp.vercel.app이다. 이 두 주소는 스킴(http vs https), 호스트(localhost vs myapp.vercel.app), 포트(3000 vs 443) 모두 다르므로 완전히 다른 출처다.
  2. R2 CORS 정책의 AllowedOrigins에 https://myapp.vercel.app만 등록하면, 배포된 사이트에서는 정상 작동하지만 로컬 개발 환경에서는 CORS 에러가 발생한다. 브라우저가 http://localhost:3000에서 보낸 요청의 Origin 헤더를 R2가 확인했을 때, 허용 목록에 해당 출처가 없기 때문이다.
  3. 반대로 http://localhost:3000만 등록하면, 로컬에서는 잘 되지만 배포 후에 CORS 에러가 발생한다. 따라서 두 출처를 모두 AllowedOrigins 배열에 포함시켜야 개발과 프로덕션 환경 모두에서 정상 작동한다.

5.2. 바이브 코딩에서 자주 발생하는 CORS 실수

  1. AI에게 파일 업로드 기능을 요청하면, 대부분 서버를 경유하는 업로드 코드를 생성한다. 로컬에서는 4.5MB 제한이 없으므로 잘 작동하지만, Vercel 배포 후 대용량 파일에서 413 에러가 발생한다. 이때 presigned URL 패턴으로 전환하면 이번에는 CORS 에러를 만나게 된다.
  2. CORS 에러를 해결하기 위해 AI에게 물어보면, 종종 AllowedOrigins: ["*"]로 모든 출처를 허용하라는 답변이 나온다. 이 설정은 당장은 작동하지만, 누구든 presigned URL만 획득하면 어떤 사이트에서든 업로드가 가능해지므로 보안상 좋지 않다. 특히 presigned URL 생성 엔드포인트에 인증이 부실한 경우, 외부에서 버킷을 남용할 수 있다.
  3. 또 하나 흔한 실수는 AllowedOrigins에 포트 번호를 빠뜨리는 것이다. http://localhosthttp://localhost:3000은 다른 출처다. 포트가 기본값(http는 80, https는 443)이 아니라면 반드시 포트 번호까지 명시해야 한다.
  4. 커스텀 도메인을 사용하는 경우도 주의해야 한다. https://myapp.comhttps://www.myapp.com은 호스트가 다르므로 별도의 출처다. 두 주소 모두에서 접근한다면 AllowedOrigins에 둘 다 포함해야 한다.

5.3. 실전 R2 CORS 설정 구성 예시

Cloudflare R2 대시보드에서 버킷의 Settings > CORS Policy로 이동한 뒤, 다음과 같은 구조로 JSON을 입력한다.

순서필드값 (업로드용 예시)
1AllowedOriginshttps://myapp.vercel.app, http://localhost:3000
2AllowedMethodsPUT, GET, HEAD
3AllowedHeadersContent-Type
4ExposeHeadersETag
5MaxAgeSeconds3600

AllowedOrigins 배열에 프로덕션 도메인과 로컬 개발 주소를 함께 나열하는 것이 핵심이다. 개발이 완료된 후에는 localhost를 제거하고 프로덕션 도메인만 남기는 것이 보안상 바람직하다.

Wrangler CLI를 사용하는 경우에는 JSON 파일을 만들어 npx wrangler r2 bucket cors set BUCKET_NAME --file cors.json 명령으로 적용할 수 있다. 설정 적용 후 반영까지 최대 30초가 걸릴 수 있으므로, 테스트 시 약간의 여유를 두는 것이 좋다.

핵심 포인트: 로컬 개발 환경(http://localhost:3000)과 배포 환경(https://myapp.vercel.app)은 서로 다른 출처이므로, R2 CORS AllowedOrigins에 두 주소를 모두 등록해야 한다. 와일드카드(*)보다 명시적 출처 지정이 보안상 안전하며, 개발 완료 후에는 localhost를 제거하는 것을 권장한다.

6. 바이브 코딩 시 CORS 관련 점검 목록

6.1. 배포 전 확인해야 할 항목

  1. 서버리스 함수를 경유하는 업로드인지, 클라이언트 직접 업로드인지 확인한다. 파일이 서버리스 함수 본문으로 전달되는 구조라면 4.5MB를 초과하는 파일에서 반드시 실패한다.
  2. R2 버킷의 CORS 정책에 프로덕션 도메인이 포함되어 있는지 확인한다. 로컬에서만 테스트하고 프로덕션 도메인을 빠뜨리면, 배포 후 모든 업로드가 실패한다.
  3. AllowedMethods에 실제 사용하는 메서드가 모두 포함되어 있는지 확인한다. 업로드에 PUT을 쓰면서 AllowedMethods에 GET만 있으면 Preflight에서 거부된다.
  4. AllowedHeaders에 요청에서 사용하는 헤더가 포함되어 있는지 확인한다. Content-Type은 파일 업로드 시 거의 필수적으로 사용되는 헤더다.
  5. 브라우저 개발자 도구의 Network 탭에서 OPTIONS 요청과 실제 요청을 분리해서 확인한다. OPTIONS 요청이 200 OK를 반환하는지, 응답 헤더에 올바른 Access-Control-Allow-Origin이 포함되어 있는지 살펴보면 문제 지점을 빠르게 찾을 수 있다.

6.2. CORS 에러 발생 시 디버깅 순서

  1. 브라우저 콘솔의 에러 메시지를 정확히 읽는다. No 'Access-Control-Allow-Origin' header is present라면 AllowedOrigins 설정 문제이고, Method not allowed라면 AllowedMethods 문제이고, Request header field is not allowed라면 AllowedHeaders 문제다.
  2. R2 CORS 정책 변경 후 최대 30초 까지 전파 지연이 있을 수 있으므로, 설정 변경 직후 테스트가 실패해도 잠시 기다렸다가 재시도한다.
  3. curl로 같은 요청을 보내서 성공하는지 확인한다. curl에서는 성공하고 브라우저에서만 실패한다면, 이는 CORS 문제가 맞으며 서버 자체의 인증·권한 문제가 아니다. 단, curl 테스트 시 -H 'Origin: https://myapp.vercel.app' 헤더를 포함해야 CORS 응답 헤더를 확인할 수 있다.

7. 마무리

위에서 살펴본 R2 CORS 설정과 Vercel 서버리스 우회 업로드의 핵심 내용을 정리하면 다음과 같다.

핵심 요약:

  • CORS는 1995년에 도입된 동일 출처 정책(SOP) 위에서 작동하는 브라우저 보안 메커니즘이며, 서버가 응답 헤더로 허용 출처를 명시해야 교차 출처 요청이 가능하다
  • Vercel 서버리스 함수에는 요청 본문 4.5MB 하드 제한이 있어, 대용량 파일은 presigned URL을 통해 R2에 직접 업로드하는 패턴이 표준적이다
  • 브라우저에서 R2로 직접 업로드하면 교차 출처 요청이 되므로, R2 버킷에 CORS 정책을 반드시 설정해야 한다
  • AllowedOrigins에는 프로덕션 도메인과 로컬 개발 주소를 모두 포함해야 두 환경 모두에서 작동한다
  • 와일드카드(*) 대신 명시적 출처 지정이 보안상 안전하며, 개발 완료 후 localhost는 제거하는 것이 좋다
  • PUT 업로드는 Preflight(OPTIONS) 요청을 거치므로 AllowedMethods와 AllowedHeaders까지 빠짐없이 설정해야 한다

R2 직접 업로드를 구현할 때는, presigned URL 생성 로직과 R2 CORS 정책 설정을 하나의 세트로 취급하는 것이 실수를 줄이는 방법이다. 바이브 코딩으로 빠르게 프로토타입을 만들더라도, CORS 구조를 이해하고 있으면 배포 후 발생하는 에러를 스스로 진단하고 해결할 수 있다.

자주 묻는 질문

  • CORS는 서버에서 적용되는 건가요, 브라우저에서 적용되는 건가요?

    CORS는 서버가 응답 헤더를 통해 허용 정책을 선언하지만, 실제로 차단을 강제하는 것은 브라우저다. 서버가 Access-Control-Allow-Origin 헤더를 보내지 않으면 브라우저가 JavaScript의 응답 접근을 차단한다. curl이나 Postman 같은 도구에서는 CORS가 적용되지 않으므로 같은 요청이 성공한다.

  • Vercel 서버리스 함수의 4.5MB 제한은 Pro 플랜에서도 동일한가요?

    Vercel 서버리스 함수의 요청 본문 크기 4.5MB 제한은 플랜과 무관하게 적용되는 하드 제한이다. Hobby, Pro, Enterprise 모두 동일하며, 이를 우회하려면 presigned URL이나 Vercel Blob 같은 직접 업로드 방식을 사용해야 한다.

  • AllowedOrigins에 와일드카드(*)를 쓰면 안 되나요?

    공개 읽기 전용 리소스에는 와일드카드를 사용해도 무방하다. 하지만 파일 업로드처럼 쓰기 작업이 포함된 경우, 와일드카드를 사용하면 어떤 사이트에서든 presigned URL로 업로드가 가능해진다. 보안상 프로덕션 도메인과 필요한 개발 환경 주소만 명시적으로 등록하는 것이 안전하다.

  • localhost를 AllowedOrigins에 등록하면 보안에 문제가 없나요?

    개발 중에는 localhost를 등록해야 로컬에서 테스트가 가능하다. 다만 프로덕션 환경에서는 localhost를 제거하는 것이 바람직하다. 외부 공격자가 자신의 로컬 환경에서 localhost로 접근하더라도 presigned URL 생성 엔드포인트에 인증이 있으면 위험이 제한되지만, 불필요한 출처는 허용 목록에서 빼는 것이 원칙이다.

  • CORS 설정을 변경했는데 바로 반영되지 않는 이유는 무엇인가요?

    R2 CORS 정책은 변경 후 최대 30초까지 전파 지연이 발생할 수 있다. 또한 브라우저가 이전 Preflight 응답을 MaxAgeSeconds 값에 따라 캐시하고 있을 수 있으므로, 브라우저 캐시를 비우거나 시크릿 모드에서 테스트하면 변경 사항을 즉시 확인할 수 있다.

  • Preflight(OPTIONS) 요청이 발생하지 않는 경우도 있나요?

    GET 요청에 커스텀 헤더가 없고, Content-Type이 text/plain이나 application/x-www-form-urlencoded 같은 단순 타입인 경우 브라우저는 Preflight 없이 바로 요청을 보내는 단순 요청으로 처리한다. 하지만 PUT 메서드를 사용하거나 Content-Type이 application/json인 경우에는 반드시 Preflight가 발생한다.