Cross-Origin Resource Sharing (CORS) is an HTTP header mechanism that lets a browser read a response from an origin other than the page it came from. Vercel Functions don't add CORS headers for you, so a browser request from https://app.example to an API on another domain fails until the server sends the right headers. Where you set those headers on Vercel changes what your policy can do, and in some cases the better fix is to skip CORS entirely by proxying the request server-to-server.
Copy link to headingWhat is CORS and when do you need it on Vercel?
CORS softens the browser's same-origin policy so frontend code at https://app.example can fetch from https://api.example when the server allows it. You need it whenever a browser reads a response across origins, which covers most setups where your frontend and API sit on different domains.
A CORS policy is a set of response headers that tell the browser what's allowed:
- Access-Control-Allow-Origin: Which origins may read the response. Use for public APIs or a specific origin such as
https://app.examplefor stricter access. - Access-Control-Allow-Methods: The HTTP verbs the client may use, such as
GET, POST, PUT, PATCH, DELETE, OPTIONS. - Access-Control-Allow-Headers: The request headers the browser may send, such as
Authorization,Content-Type, orX-CSRF-Token. - Access-Control-Allow-Credentials: Whether the browser may send cookies or HTTP auth headers. Set it to
trueonly with an explicit, non- origin. - Access-Control-Max-Age: How long, in seconds, the browser can cache the preflight response, such as
86400for 24 hours.
Browsers send a preflight OPTIONS request before requests that use a method other than GET, HEAD, or POST, or that carry custom headers. The preflight has to return these headers, or the real request never runs.
Copy link to headingWhere to configure CORS headers on Vercel
You can set CORS headers in several places on Vercel, from static rules at the CDN to runtime logic in middleware. Choose the layer by whether your policy is static, meaning the same headers on every request, or dynamic, meaning the allowed origin depends on the request. Static rules belong in configuration, and runtime decisions belong in middleware or function code.
Copy link to headingSet CORS headers in vercel.json (CDN layer)
The headers property in vercel.json applies CORS headers at the CDN before your function runs. Use it for static rules across a path pattern:
To match one specific origin at this layer, add a has condition on the Origin header:
The vercel.json layer supports only static values, so it can't validate an origin against an allowlist or switch behavior by environment. For that, use Routing Middleware.
Copy link to headingSet CORS headers in next.config.ts (framework layer)
Next.js projects can set the same headers in framework config with an async headers() function that mirrors the vercel.json shape:
Path matching supports modifiers on named parameters:
- (zero or more): Matches
/blog,/blog/a, and/blog/a/b/cfor a pattern like/blog/:slug*. +(one or more): Matches one or more path segments after the parameter.?(zero or one): Matches an optional single segment.
You can also wrap a regular expression in parentheses after a parameter for finer matching. These values are static like vercel.json, so use middleware when the allowed origin depends on the request.
Copy link to headingValidate origins at runtime with Routing Middleware
When the allowed origin depends on the request, such as an environment-specific allowlist or several allowed origins, validate it in Routing Middleware. Middleware runs on Vercel's network before the request reaches your function, so it can answer a preflight OPTIONS before your function starts. Add a middleware.ts file at the root of your project:
The matcher values must be constants so Vercel can resolve them at build time, and any dynamic value like a variable is ignored.
Preview deployments get a new URL on every push, so a hardcoded production origin fails CORS on previews. Drive allowedOrigins from an environment variable, and use the VERCEL_PROJECT_PRODUCTION_URL system environment variable for your production domain that stays set even during preview deployments. For a single endpoint, you can set the same headers directly in a route handler.
Copy link to headingSet CORS headers in a route handler (function layer)
In the Next.js App Router, a route handler sets CORS headers with standard Web APIs, which works well when one endpoint needs its own policy:
This pattern fits a single endpoint well. To apply CORS across many handlers without repeating code, use vercel.json, framework config, or middleware instead.
Copy link to headingHow to skip CORS on Vercel with a rewrite proxy
Before configuring any headers, check whether you need them at all. A rewrite can proxy a browser request to an external origin server-to-server, so the browser sees a same-origin response and never sends a preflight:
These rules run at the routing layer before the request reaches a function, and they apply when you deploy the change. This helps when you integrate a third-party service that doesn't handle preflight OPTIONS requests well, because the browser-facing preflight goes away.
Server Components and Server Actions avoid CORS in the same way. A request made on the server isn't subject to the browser's same-origin policy, so fetching data in a Server Component removes the cross-origin problem from the browser path.
Copy link to headingHow to fix CORS preflight blocked by Deployment Protection
When Deployment Protection through Vercel Authentication, Password Protection, or Trusted IPs is on, an unauthenticated preflight OPTIONS request gets a 401 from Vercel before your middleware or function runs. The browser never sees your CORS headers, so the actual request is never sent.
The OPTIONS Allowlist fixes this case. It exempts specific paths from Deployment Protection for OPTIONS requests only, matching by path prefix, so adding /api covers /api/v1/users and every path under it. For new projects, /api/* is on the allowlist by default.
Verify the fix with curl against your deployment:
Look for a 200 status and the expected CORS headers in the response. A 401 means the path isn't on the allowlist yet.
Copy link to headingCommon CORS mistakes and security gotchas on Vercel
A working CORS setup can still break, usually from caching, credentials, or middleware behavior.
Copy link to headingCache poisoning from origin-dependent CORS
When your policy returns a different Access-Control-Allow-Origin per origin, CDN caching can serve a response approved for one origin to a request from another, and the browser blocks it. This happens because the Origin request header isn't part of the CDN cache key by default. Add a Vary: Origin header so the cache keys on the origin, and set Cache-Control: no-cache on responses whose CORS headers vary by origin. A single static origin or * stays safe to cache, because the value doesn't change per request.
Copy link to headingWildcards, credentials, and the null origin
Access-Control-Allow-Origin: * is fine for public, unauthenticated APIs. It becomes a problem only alongside Access-Control-Allow-Credentials: true, a combination browsers already reject. Anyone can already reach public data with curl or any server-side HTTP client, since CORS is a browser-enforced policy rather than an authorization mechanism.
The null origin is more dangerous. Some setups allowlist null for local or sandboxed contexts, but an attacker can send Origin: null from a sandboxed iframe. If your server reflects null with credentials allowed, it's as exposed as a wildcard, so don't allowlist null origins.
When you need credentials, Access-Control-Allow-Origin must name one exact origin. To support several credentialed origins, validate the incoming Origin against your allowlist in middleware, echo back the matched origin, and set Vary: Origin. Anchor any subdomain regex to the end of the string, or a host like attacker.com.example.com can slip past a loose pattern.
On Enterprise plans, the Conformance rule NO_CORS_HEADERS flags CORS configuration for review. It catches values such as Access-Control-Allow-Origin: '*' and Access-Control-Allow-Credentials: true so a reviewer can confirm the policy is intentional.
Copy link to headingMiddleware limits: Server Actions and cold-start preflight
Middleware is the most flexible CORS layer on Vercel, but a few cases need handling:
- Server Action conflicts: Returning a response from middleware breaks Next.js Server Actions. Check for the action request first with
request.headers.get('next-action'), then let those requests pass through instead of returning early. - Cold-start preflight timeouts: A preflight that hits a cold function can time out before the CORS headers return, which the browser shows as a CORS error. Handle preflight in middleware, or enable Fluid compute so warm instances keep the handler responsive.
- Authentication belongs in your function; use middleware for lightweight tasks like CORS headers and proxy rewrites. Keep authentication and authorization in function or application code.
With those cases handled, middleware covers the runtime CORS logic that static configuration can't.
Copy link to headingNext steps
With your CORS policy in place, deploy your project and verify the preflight and the real request against your live URL. Start a new project or browse the templates to build on Vercel.
Copy link to headingRelated resources
- Vercel Functions
- Routing Middleware
- OPTIONS Allowlist
- System environment variables
- Project configuration
- Conformance rules
Copy link to headingFrequently asked questions
Copy link to headingDo Vercel Functions add CORS headers automatically?
No. Vercel Functions don't add CORS headers for you, whether you run them standalone or through a framework. Every function type needs an explicit CORS policy, set in the function code, vercel.json, your framework config, or Routing Middleware. Without those headers, a browser blocks cross-origin reads of the response.
Copy link to headingCan I use Access-Control-Allow-Origin: \ with credentials?
No. Browsers reject any response that combines Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. When a request requires credentials, the origin must be exactly one value. Validate the incoming Origin header against your allowlist in middleware, echo back the matched origin, and add a Vary: Origin header.
Copy link to headingWhy does my CORS work in production but fail in preview deployments?
Preview deployments get a new URL on every push. If your allowlist only includes your production domain, preview requests fail the origin check. Drive your allowed origins from an environment variable, and reference the VERCEL_PROJECT_PRODUCTION_URL system environment variable, which stays set even on preview deployments, to detect the deployment context.
Copy link to headingHow do I handle CORS for streaming (SSE) responses on Vercel?
Set CORS headers on the initial response before streaming starts. Send Access-Control-Allow-Origin alongside Content-Type: text/event-stream and Cache-Control: no-cache in the response headers. If middleware compresses the stream, adding Content-Encoding: none resolves Server-Sent Events compatibility issues so the browser reads the stream.