How to fix CORS error in Next.js?

export async function POST(req: NextRequest) {
// req.headers.set("Access-Control-Allow-Origin", "http://localhost:3000/")
req.headers.set("Access-Control-Allow-Origin", "*")
req.headers.set("Access-Control-Allow-Methods", "*")

cors({ origin: "*" })

const { env, userId, secretKey } = await req.json()

if (!userId) {
return NextResponse.json({ error: "userId is missing" }, { status: 400, headers: corsHeaders })
}
export async function POST(req: NextRequest) {
// req.headers.set("Access-Control-Allow-Origin", "http://localhost:3000/")
req.headers.set("Access-Control-Allow-Origin", "*")
req.headers.set("Access-Control-Allow-Methods", "*")

cors({ origin: "*" })

const { env, userId, secretKey } = await req.json()

if (!userId) {
return NextResponse.json({ error: "userId is missing" }, { status: 400, headers: corsHeaders })
}
No description
No description
37 Replies
dys 🐙
dys 🐙2mo ago
What is corsHeaders defined as? Also, you are only showing the error response. What is the successful response? FInally, it is the OPTIONS method that is failing. Does defining POST automatically handle OPTIONS?
Nikita
Nikita2mo ago
I don't understand what expected answer you want to get Where can I check it? I think you find something you asked for I tried export async function OPTIONS(req: NextRequest, res: NextResponse) {
import { NextRequest, NextResponse } from "next/server"
import cors from "cors"
import { corsHeaders } from "@/consts/corsHeaders"

export async function POST(req: NextRequest, res: NextResponse) {
console.log(4, "req.method - ", req.method)
cors({
origin: ["https://outreach-tool.online", "http://localhost:3000"],
methods: ["GET", "POST", "OPTIONS"],
allowedHeaders: ["Content-Type", "x-forwarded-for"],
credentials: true,
})

return NextResponse.json({ res }, { headers: corsHeaders })
}
import { NextRequest, NextResponse } from "next/server"
import cors from "cors"
import { corsHeaders } from "@/consts/corsHeaders"

export async function POST(req: NextRequest, res: NextResponse) {
console.log(4, "req.method - ", req.method)
cors({
origin: ["https://outreach-tool.online", "http://localhost:3000"],
methods: ["GET", "POST", "OPTIONS"],
allowedHeaders: ["Content-Type", "x-forwarded-for"],
credentials: true,
})

return NextResponse.json({ res }, { headers: corsHeaders })
}
return NextResponse.json({ res }, { headers: corsHeaders })
dys 🐙
dys 🐙2mo ago
After doing a little reading, I find nothing that suggests that the cors middleware will work like you are using it. There's a nextjs-cors package that seems to adapt the Express cors middleware to work with Next.js, though it's relatively old. Were I you, I'd try creating a middleware.ts and populating it as described here (though you can statically define the CORS options rather than pulling everything in from the environment to make it simpler).
Medium
3 Ways To Configure CORS for NextJS 13 App Router API Route Handlers
Configure Cross-Origin Resource Sharing For NextJS 13 App Router
GitHub
GitHub - yonycalsin/nextjs-cors: :tada: nextjs-cors is a node.js pa...
:tada: nextjs-cors is a node.js package to provide a Connect/Express middleware that can be used to enable CORS with various options :rocket: - yonycalsin/nextjs-cors
Nikita
Nikita2mo ago
It doesn't work for me as well
import NextCors from "nextjs-cors"
import { NextApiRequest, NextApiResponse } from "next"

export async function POST(req: NextApiRequest, res: NextApiResponse) {
console.log(4, "req.method - ", req.method)

await NextCors(req, res, {
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
origin: "https://localhost:3000",
optionsSuccessStatus: 200,
})

return res.json({ res })
}
import NextCors from "nextjs-cors"
import { NextApiRequest, NextApiResponse } from "next"

export async function POST(req: NextApiRequest, res: NextApiResponse) {
console.log(4, "req.method - ", req.method)

await NextCors(req, res, {
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
origin: "https://localhost:3000",
optionsSuccessStatus: 200,
})

return res.json({ res })
}
No description
Nikita
Nikita2mo ago
My middleware.ts looks like as in medium post
import { NextResponse, type NextRequest } from "next/server"

const corsOptions: {
allowedMethods: string[]
allowedOrigins: string[]
allowedHeaders: string[]
exposedHeaders: string[]
maxAge?: number
credentials: boolean
} = {
allowedMethods: (process.env?.ALLOWED_METHODS || "").split(","),
allowedOrigins: (process.env?.ALLOWED_ORIGIN || "").split(","),
allowedHeaders: (process.env?.ALLOWED_HEADERS || "").split(","),
exposedHeaders: (process.env?.EXPOSED_HEADERS || "").split(","),
maxAge: (process.env?.MAX_AGE && parseInt(process.env?.MAX_AGE)) || undefined, // 60 * 60 * 24 * 30, // 30 days
credentials: process.env?.CREDENTIALS == "true",
}

// Middleware
// ========================================================
// This function can be marked `async` if using `await` inside
export async function middleware(request: NextRequest) {
// Response
const response = NextResponse.next()

// Allowed origins check
const origin = request.headers.get("origin") ?? ""
if (corsOptions.allowedOrigins.includes("*") || corsOptions.allowedOrigins.includes(origin)) {
response.headers.set("Access-Control-Allow-Origin", origin)
}

// Set default CORS headers
response.headers.set("Access-Control-Allow-Credentials", corsOptions.credentials.toString())
response.headers.set("Access-Control-Allow-Methods", corsOptions.allowedMethods.join(","))
response.headers.set("Access-Control-Allow-Headers", corsOptions.allowedHeaders.join(","))
response.headers.set("Access-Control-Expose-Headers", corsOptions.exposedHeaders.join(","))
response.headers.set("Access-Control-Max-Age", corsOptions.maxAge?.toString() ?? "")

// Return
return response
}

// See "Matching Paths" below to learn more
export const config = {
matcher: "/api/:path*",
}
import { NextResponse, type NextRequest } from "next/server"

const corsOptions: {
allowedMethods: string[]
allowedOrigins: string[]
allowedHeaders: string[]
exposedHeaders: string[]
maxAge?: number
credentials: boolean
} = {
allowedMethods: (process.env?.ALLOWED_METHODS || "").split(","),
allowedOrigins: (process.env?.ALLOWED_ORIGIN || "").split(","),
allowedHeaders: (process.env?.ALLOWED_HEADERS || "").split(","),
exposedHeaders: (process.env?.EXPOSED_HEADERS || "").split(","),
maxAge: (process.env?.MAX_AGE && parseInt(process.env?.MAX_AGE)) || undefined, // 60 * 60 * 24 * 30, // 30 days
credentials: process.env?.CREDENTIALS == "true",
}

// Middleware
// ========================================================
// This function can be marked `async` if using `await` inside
export async function middleware(request: NextRequest) {
// Response
const response = NextResponse.next()

// Allowed origins check
const origin = request.headers.get("origin") ?? ""
if (corsOptions.allowedOrigins.includes("*") || corsOptions.allowedOrigins.includes(origin)) {
response.headers.set("Access-Control-Allow-Origin", origin)
}

// Set default CORS headers
response.headers.set("Access-Control-Allow-Credentials", corsOptions.credentials.toString())
response.headers.set("Access-Control-Allow-Methods", corsOptions.allowedMethods.join(","))
response.headers.set("Access-Control-Allow-Headers", corsOptions.allowedHeaders.join(","))
response.headers.set("Access-Control-Expose-Headers", corsOptions.exposedHeaders.join(","))
response.headers.set("Access-Control-Max-Age", corsOptions.maxAge?.toString() ?? "")

// Return
return response
}

// See "Matching Paths" below to learn more
export const config = {
matcher: "/api/:path*",
}
I have middleware.ts in project I'm making request to
dys 🐙
dys 🐙2mo ago
Your server is on port 3001.
Nikita
Nikita2mo ago
yes I'm doing request from 3000 to 3001
dys 🐙
dys 🐙2mo ago
There's no CORS headers though, according to the error. Did you define the appropriate environment variables in a .env file?
Nikita
Nikita2mo ago
what? I have .env.local file but how its related to CORS? In project 1 (3000) I'm doing request like this
const response = await fetch(`http://localhost:3001/api/test`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Forwarded-For": "http://localhost:3000",
},
body: JSON.stringify({ env, userId, secretKey }),
cache: "no-cache",
})
const response = await fetch(`http://localhost:3001/api/test`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Forwarded-For": "http://localhost:3000",
},
body: JSON.stringify({ env, userId, secretKey }),
cache: "no-cache",
})
dys 🐙
dys 🐙2mo ago
If you look at the corsOptions variable, it is pulling its configuration from the environment.
Nikita
Nikita2mo ago
I think its wrong
env.local
ALLOWED_METHODS='POST,GET'
ALLOWED_ORIGIN='http://localhost:3000, https://outreach-tool.online'
env.local
ALLOWED_METHODS='POST,GET'
ALLOWED_ORIGIN='http://localhost:3000, https://outreach-tool.online'
I want to allow multiple things
dys 🐙
dys 🐙2mo ago
You have to include OPTIONS.
Nikita
Nikita2mo ago
Ok Also I think that its worth to mention that I don't have
allowedHeaders: (process.env?.ALLOWED_HEADERS || "").split(","),
exposedHeaders: (process.env?.EXPOSED_HEADERS || "").split(","),
allowedHeaders: (process.env?.ALLOWED_HEADERS || "").split(","),
exposedHeaders: (process.env?.EXPOSED_HEADERS || "").split(","),
I simply don't know what should I put in there Like that?
ALLOWED_METHODS='OPTIONS'
ALLOWED_ORIGIN='http://localhost:3000, https://outreach-tool.online'
ALLOWED_METHODS='OPTIONS'
ALLOWED_ORIGIN='http://localhost:3000, https://outreach-tool.online'
Even if I copy pasted - still same error
ALLOWED_METHODS="GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS"
ALLOWED_ORIGIN="https://outreach-tool.online,http://localhost:3000"
ALLOWED_HEADERS="Content-Type, Authorization"
EXPOSED_HEADERS=""
MAX_AGE="86400" # 60 * 60 * 24 = 24 hours
CREDENTIALS="true"
DOMAIN_URL="http://localhost:3001"
ALLOWED_METHODS="GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS"
ALLOWED_ORIGIN="https://outreach-tool.online,http://localhost:3000"
ALLOWED_HEADERS="Content-Type, Authorization"
EXPOSED_HEADERS=""
MAX_AGE="86400" # 60 * 60 * 24 = 24 hours
CREDENTIALS="true"
DOMAIN_URL="http://localhost:3001"
dys 🐙
dys 🐙2mo ago
Have you restarted the dev server? (Don't know if this will have an effect, but it never hurts.)
Nikita
Nikita2mo ago
No description
Nikita
Nikita2mo ago
I tried to clear .next and restart pnpm dev on project 1 (3000)
dys 🐙
dys 🐙2mo ago
Well, if anything, you need to restart the server setting the CORS headers, so the one on 3001.
Nikita
Nikita2mo ago
ok now I deleted .next and restarted project 2 (3001) using pnpm dev And see new error
No description
dys 🐙
dys 🐙2mo ago
What is the HTTP status? (From the Network tab in the developer's tools.)
Nikita
Nikita2mo ago
IDK
No description
Nikita
Nikita2mo ago
seems http
dys 🐙
dys 🐙2mo ago
It looks like your request is finally setting the headers, but the request is failing.
Nikita
Nikita2mo ago
No description
Nikita
Nikita2mo ago
Here is my API route on project 2 (3001) api/test/route.ts
import NextCors from "nextjs-cors"
import { NextApiRequest, NextApiResponse } from "next"
import { NextResponse } from "next/server"

export async function POST(req: NextApiRequest, res: NextApiResponse) {
console.log(4, "req.method - ", req.method)

await NextCors(req, res, {
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
origin: "https://localhost:3000",
optionsSuccessStatus: 200,
})

return NextResponse.json({}, { status: 200 })
}
import NextCors from "nextjs-cors"
import { NextApiRequest, NextApiResponse } from "next"
import { NextResponse } from "next/server"

export async function POST(req: NextApiRequest, res: NextApiResponse) {
console.log(4, "req.method - ", req.method)

await NextCors(req, res, {
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
origin: "https://localhost:3000",
optionsSuccessStatus: 200,
})

return NextResponse.json({}, { status: 200 })
}
dys 🐙
dys 🐙2mo ago
You're not defining OPTIONS, so it says HTTP 405: Method Not Allowed.
Nikita
Nikita2mo ago
How to define OPTIONS? methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE","OPTIONS"], You mean this?
await NextCors(req, res, {
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"],
origin: "https://localhost:3000",
optionsSuccessStatus: 200,
})
await NextCors(req, res, {
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"],
origin: "https://localhost:3000",
optionsSuccessStatus: 200,
})
Nikita
Nikita2mo ago
Still same
No description
dys 🐙
dys 🐙2mo ago
Nope, you're defining a POST handler, but not OPTIONS.
Nikita
Nikita2mo ago
Like this?
import NextCors from "nextjs-cors"
import { NextApiRequest, NextApiResponse } from "next"
import { NextResponse } from "next/server"

export async function OPTIONS(req: NextApiRequest, res: NextApiResponse) {
console.log(4, "req.method - ", req.method)

await NextCors(req, res, {
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"],
origin: "https://localhost:3000",
optionsSuccessStatus: 200,
})

return NextResponse.json({}, { status: 200 })
}
import NextCors from "nextjs-cors"
import { NextApiRequest, NextApiResponse } from "next"
import { NextResponse } from "next/server"

export async function OPTIONS(req: NextApiRequest, res: NextApiResponse) {
console.log(4, "req.method - ", req.method)

await NextCors(req, res, {
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"],
origin: "https://localhost:3000",
optionsSuccessStatus: 200,
})

return NextResponse.json({}, { status: 200 })
}
Nikita
Nikita2mo ago
Same
No description
dys 🐙
dys 🐙2mo ago
The middleware should obviate the need for NextCors.
Nikita
Nikita2mo ago
Also I got error on server
4 req.method - OPTIONS
⨯ TypeError: res.setHeader is not a function
4 req.method - OPTIONS
⨯ TypeError: res.setHeader is not a function
May I call you in discord?
dys 🐙
dys 🐙2mo ago
Sure. There's a #voice-chat channel here we could use.
Nikita
Nikita2mo ago
Solution project 2 (3001) api/test/route.ts
import { NextApiRequest, NextApiResponse } from "next"
import { NextResponse } from "next/server"

export async function OPTIONS(req: NextApiRequest, res: NextApiResponse) {
return NextResponse.json({}, { status: 200 })
}

export async function POST(req: NextApiRequest, res: NextApiResponse) {
console.log(4, "req.method - ", req.method)

return NextResponse.json({}, { status: 200 })
}
import { NextApiRequest, NextApiResponse } from "next"
import { NextResponse } from "next/server"

export async function OPTIONS(req: NextApiRequest, res: NextApiResponse) {
return NextResponse.json({}, { status: 200 })
}

export async function POST(req: NextApiRequest, res: NextApiResponse) {
console.log(4, "req.method - ", req.method)

return NextResponse.json({}, { status: 200 })
}
project 2 (3001) middleware.ts
import { NextResponse, type NextRequest } from "next/server"

const corsOptions: {
allowedMethods: string[]
allowedOrigins: string[]
allowedHeaders: string[]
exposedHeaders: string[]
maxAge?: number
credentials: boolean
} = {
allowedMethods: (process.env?.ALLOWED_METHODS || "").split(","),
allowedOrigins: (process.env?.ALLOWED_ORIGIN || "").split(","),
allowedHeaders: (process.env?.ALLOWED_HEADERS || "").split(","),
exposedHeaders: (process.env?.EXPOSED_HEADERS || "").split(","),
maxAge: (process.env?.MAX_AGE && parseInt(process.env?.MAX_AGE)) || undefined, // 60 * 60 * 24 * 30, // 30 days
credentials: process.env?.CREDENTIALS == "true",
}

// Middleware
// ========================================================
// This function can be marked `async` if using `await` inside
export async function middleware(request: NextRequest) {
// Response
const response = NextResponse.next()

// Allowed origins check
const origin = request.headers.get("origin") ?? ""
if (corsOptions.allowedOrigins.includes("*") || corsOptions.allowedOrigins.includes(origin)) {
response.headers.set("Access-Control-Allow-Origin", origin)
}

// Set default CORS headers
response.headers.set("Access-Control-Allow-Credentials", corsOptions.credentials.toString())
response.headers.set("Access-Control-Allow-Methods", corsOptions.allowedMethods.join(","))
response.headers.set("Access-Control-Allow-Headers", corsOptions.allowedHeaders.join(","))
response.headers.set("Access-Control-Expose-Headers", corsOptions.exposedHeaders.join(","))
response.headers.set("Access-Control-Max-Age", corsOptions.maxAge?.toString() ?? "")

// Return
return response
}

// See "Matching Paths" below to learn more
export const config = {
matcher: "/api/:path*",
}
import { NextResponse, type NextRequest } from "next/server"

const corsOptions: {
allowedMethods: string[]
allowedOrigins: string[]
allowedHeaders: string[]
exposedHeaders: string[]
maxAge?: number
credentials: boolean
} = {
allowedMethods: (process.env?.ALLOWED_METHODS || "").split(","),
allowedOrigins: (process.env?.ALLOWED_ORIGIN || "").split(","),
allowedHeaders: (process.env?.ALLOWED_HEADERS || "").split(","),
exposedHeaders: (process.env?.EXPOSED_HEADERS || "").split(","),
maxAge: (process.env?.MAX_AGE && parseInt(process.env?.MAX_AGE)) || undefined, // 60 * 60 * 24 * 30, // 30 days
credentials: process.env?.CREDENTIALS == "true",
}

// Middleware
// ========================================================
// This function can be marked `async` if using `await` inside
export async function middleware(request: NextRequest) {
// Response
const response = NextResponse.next()

// Allowed origins check
const origin = request.headers.get("origin") ?? ""
if (corsOptions.allowedOrigins.includes("*") || corsOptions.allowedOrigins.includes(origin)) {
response.headers.set("Access-Control-Allow-Origin", origin)
}

// Set default CORS headers
response.headers.set("Access-Control-Allow-Credentials", corsOptions.credentials.toString())
response.headers.set("Access-Control-Allow-Methods", corsOptions.allowedMethods.join(","))
response.headers.set("Access-Control-Allow-Headers", corsOptions.allowedHeaders.join(","))
response.headers.set("Access-Control-Expose-Headers", corsOptions.exposedHeaders.join(","))
response.headers.set("Access-Control-Max-Age", corsOptions.maxAge?.toString() ?? "")

// Return
return response
}

// See "Matching Paths" below to learn more
export const config = {
matcher: "/api/:path*",
}
project 2 (3001) .env.local
ALLOWED_METHODS="GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS"
ALLOWED_ORIGIN="https://outreach-tool.online,http://localhost:3000"
ALLOWED_HEADERS="Content-Type, Authorization, X-Forwarded-For"
EXPOSED_HEADERS=""
MAX_AGE="86400" # 60 * 60 * 24 = 24 hours
CREDENTIALS="true"
DOMAIN_URL="http://localhost:3001"
ALLOWED_METHODS="GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS"
ALLOWED_ORIGIN="https://outreach-tool.online,http://localhost:3000"
ALLOWED_HEADERS="Content-Type, Authorization, X-Forwarded-For"
EXPOSED_HEADERS=""
MAX_AGE="86400" # 60 * 60 * 24 = 24 hours
CREDENTIALS="true"
DOMAIN_URL="http://localhost:3001"
I'm doing request from project 1 (3000) to project 2 (3001) like this
const response = await fetch(`http://localhost:3001/api/test`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Forwarded-For": "http://localhost:3000",
},
body: JSON.stringify({ env, userId, secretKey }),
cache: "no-cache",
})
const response = await fetch(`http://localhost:3001/api/test`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Forwarded-For": "http://localhost:3000",
},
body: JSON.stringify({ env, userId, secretKey }),
cache: "no-cache",
})
Nikita
Nikita2mo ago
Today I got this error - how to fix that?
No description
Nikita
Nikita2mo ago
Disable proxy
No description
hgs
hgs2mo ago
In nextjs, rewrite and middleware could solve it
Want results from more Discord servers?
Add your server