MENU
CORS
Cross-Origin Resource Sharing (CORS) headers define which sites and methods are allowed to access a page.You can configure CORS headers for a specific Route Handler using standard Web API methods:
* refers to all origins.
route.tsx:
route.tsx:
export async function GET(request: Request) {
return new Response('Hello, Next.js!', {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
})
}
The following is a handy CORS library file.
(Courtesy of the Vercel's GitHub repository here.)
lib_cors.tsx:
To add CORS headers to multiple Route Handlers, you can use Middleware or the next.config.js file.
(Courtesy of the Vercel's GitHub repository here.)
lib_cors.tsx:
/**
* Multi purpose CORS lib.
* Note: Based on the `cors` package in npm but using only
* web APIs. Feel free to use it in your own projects.
*/
type StaticOrigin = boolean | string | RegExp | (boolean | string | RegExp)[]
type OriginFn = (
origin: string | undefined,
req: Request
) => StaticOrigin | Promise<StaticOrigin>
interface CorsOptions {
origin?: StaticOrigin | OriginFn
methods?: string | string[]
allowedHeaders?: string | string[]
exposedHeaders?: string | string[]
credentials?: boolean
maxAge?: number
preflightContinue?: boolean
optionsSuccessStatus?: number
}
const defaultOptions: CorsOptions = {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
}
function isOriginAllowed(origin: string, allowed: StaticOrigin): boolean {
return Array.isArray(allowed)
? allowed.some((o) => isOriginAllowed(origin, o))
: typeof allowed === 'string'
? origin === allowed
: allowed instanceof RegExp
? allowed.test(origin)
: !!allowed
}
function getOriginHeaders(reqOrigin: string | undefined, origin: StaticOrigin) {
const headers = new Headers()
if (origin === '*') {
// Allow any origin
headers.set('Access-Control-Allow-Origin', '*')
} else if (typeof origin === 'string') {
// Fixed origin
headers.set('Access-Control-Allow-Origin', origin)
headers.append('Vary', 'Origin')
} else {
const allowed = isOriginAllowed(reqOrigin ?? '', origin)
if (allowed && reqOrigin) {
headers.set('Access-Control-Allow-Origin', reqOrigin)
}
headers.append('Vary', 'Origin')
}
return headers
}
// originHeadersFromReq
async function originHeadersFromReq(
req: Request,
origin: StaticOrigin | OriginFn
) {
const reqOrigin = req.headers.get('Origin') || undefined
const value =
typeof origin === 'function' ? await origin(reqOrigin, req) : origin
if (!value) return
return getOriginHeaders(reqOrigin, value)
}
function getAllowedHeaders(req: Request, allowed?: string | string[]) {
const headers = new Headers()
if (!allowed) {
allowed = req.headers.get('Access-Control-Request-Headers')!
headers.append('Vary', 'Access-Control-Request-Headers')
} else if (Array.isArray(allowed)) {
// If the allowed headers is an array, turn it into a string
allowed = allowed.join(',')
}
if (allowed) {
headers.set('Access-Control-Allow-Headers', allowed)
}
return headers
}
export default async function cors(
req: Request,
res: Response,
options?: CorsOptions
) {
const opts = { ...defaultOptions, ...options }
const { headers } = res
const originHeaders = await originHeadersFromReq(req, opts.origin ?? false)
const mergeHeaders = (v: string, k: string) => {
if (k === 'Vary') headers.append(k, v)
else headers.set(k, v)
}
// If there's no origin we won't touch the response
if (!originHeaders) return res
originHeaders.forEach(mergeHeaders)
if (opts.credentials) {
headers.set('Access-Control-Allow-Credentials', 'true')
}
const exposed = Array.isArray(opts.exposedHeaders)
? opts.exposedHeaders.join(',')
: opts.exposedHeaders
if (exposed) {
headers.set('Access-Control-Expose-Headers', exposed)
}
// Handle the preflight request
if (req.method === 'OPTIONS') {
if (opts.methods) {
const methods = Array.isArray(opts.methods)
? opts.methods.join(',')
: opts.methods
headers.set('Access-Control-Allow-Methods', methods)
}
getAllowedHeaders(req, opts.allowedHeaders).forEach(mergeHeaders)
if (typeof opts.maxAge === 'number') {
headers.set('Access-Control-Max-Age', String(opts.maxAge))
}
if (opts.preflightContinue) return res
headers.set('Content-Length', '0')
return new Response(null, { status: opts.optionsSuccessStatus, headers })
}
// If we got here, it's a normal request
return res
}
export function initCors(options?: CorsOptions) {
return (req: Request, res: Response) => cors(req, res, options)
}