[{"data":1,"prerenderedAt":309},["ShallowReactive",2],{"blog-how-to/setup-cors-properly":3},{"id":4,"title":5,"body":6,"category":290,"date":291,"dateModified":291,"description":292,"draft":293,"extension":294,"faq":295,"featured":293,"headerVariant":296,"image":295,"keywords":295,"meta":297,"navigation":298,"ogDescription":299,"ogTitle":295,"path":300,"readTime":295,"schemaOrg":301,"schemaType":302,"seo":303,"sitemap":304,"stem":305,"tags":306,"twitterCard":307,"__hash__":308},"blog/blog/how-to/setup-cors-properly.md","How to Set Up CORS Properly",{"type":7,"value":8,"toc":271},"minimark",[9,13,17,21,35,40,51,55,58,68,75,79,84,90,94,100,104,107,113,117,123,127,130,143,146,152,156,159,180,184,188,194,198,204,208,246,252],[10,11],"category-badge",{"category":12},"How-To Guide",[14,15,5],"h1",{"id":16},"how-to-set-up-cors-properly",[18,19,20],"p",{},"Stop seeing \"blocked by CORS policy\" without compromising security",[22,23,24,27],"tldr",{},[18,25,26],{},"TL;DR",[18,28,29,30,34],{},"Never use ",[31,32,33],"code",{},"Access-Control-Allow-Origin: *"," with credentials. Whitelist specific domains. Handle preflight OPTIONS requests. For same-origin apps (frontend and API on same domain), you might not need CORS at all.",[36,37,39],"h2",{"id":38},"what-is-cors","What is CORS?",[18,41,42,43,46,47,50],{},"Cross-Origin Resource Sharing (CORS) is a browser security feature that blocks requests from one domain to another unless the server explicitly allows it. If your frontend is on ",[31,44,45],{},"app.example.com"," and your API is on ",[31,48,49],{},"api.example.com",", you need CORS.",[36,52,54],{"id":53},"the-most-common-mistake","The Most Common Mistake",[18,56,57],{},"Never Do This in Production",[59,60,65],"pre",{"className":61,"code":63,"language":64},[62],"language-text","// DANGEROUS: Allows any website to call your API\nres.setHeader('Access-Control-Allow-Origin', '*');\nres.setHeader('Access-Control-Allow-Credentials', 'true'); // This won't even work!\n","text",[31,66,63],{"__ignoreMap":67},"",[18,69,70,71,74],{},"Using ",[31,72,73],{},"*"," with credentials is not just insecure, it's actually invalid. Browsers will reject this combination.",[36,76,78],{"id":77},"nextjs-app-router","Next.js App Router",[80,81,83],"h3",{"id":82},"option-1-route-handler-headers","Option 1: Route Handler Headers",[59,85,88],{"className":86,"code":87,"language":64},[62],"// app/api/data/route.ts\nconst ALLOWED_ORIGINS = [\n  'https://myapp.com',\n  'https://www.myapp.com',\n  process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : '',\n].filter(Boolean);\n\nfunction getCorsHeaders(origin: string | null) {\n  const headers: Record\u003Cstring, string> = {\n    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n    'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n  };\n\n  if (origin && ALLOWED_ORIGINS.includes(origin)) {\n    headers['Access-Control-Allow-Origin'] = origin;\n    headers['Access-Control-Allow-Credentials'] = 'true';\n  }\n\n  return headers;\n}\n\nexport async function OPTIONS(request: Request) {\n  const origin = request.headers.get('origin');\n  return new Response(null, {\n    status: 204,\n    headers: getCorsHeaders(origin),\n  });\n}\n\nexport async function GET(request: Request) {\n  const origin = request.headers.get('origin');\n\n  // Your logic here\n  const data = { message: 'Hello' };\n\n  return Response.json(data, {\n    headers: getCorsHeaders(origin),\n  });\n}\n",[31,89,87],{"__ignoreMap":67},[80,91,93],{"id":92},"option-2-nextjs-middleware","Option 2: Next.js Middleware",[59,95,98],{"className":96,"code":97,"language":64},[62],"// middleware.ts\nimport { NextRequest, NextResponse } from 'next/server';\n\nconst ALLOWED_ORIGINS = [\n  'https://myapp.com',\n  'https://www.myapp.com',\n];\n\nexport function middleware(request: NextRequest) {\n  const origin = request.headers.get('origin');\n  const isAllowedOrigin = origin && ALLOWED_ORIGINS.includes(origin);\n\n  // Handle preflight\n  if (request.method === 'OPTIONS') {\n    return new NextResponse(null, {\n      status: 204,\n      headers: {\n        'Access-Control-Allow-Origin': isAllowedOrigin ? origin : '',\n        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',\n        'Access-Control-Allow-Headers': 'Content-Type, Authorization',\n        'Access-Control-Max-Age': '86400',\n      },\n    });\n  }\n\n  const response = NextResponse.next();\n\n  if (isAllowedOrigin) {\n    response.headers.set('Access-Control-Allow-Origin', origin);\n    response.headers.set('Access-Control-Allow-Credentials', 'true');\n  }\n\n  return response;\n}\n\nexport const config = {\n  matcher: '/api/:path*',\n};\n",[31,99,97],{"__ignoreMap":67},[36,101,103],{"id":102},"expressjs","Express.js",[18,105,106],{},"Secure Express CORS Setup",[59,108,111],{"className":109,"code":110,"language":64},[62],"import cors from 'cors';\nimport express from 'express';\n\nconst app = express();\n\nconst corsOptions = {\n  origin: (origin, callback) => {\n    const allowedOrigins = [\n      'https://myapp.com',\n      'https://www.myapp.com',\n    ];\n\n    // Allow requests with no origin (mobile apps, curl, etc.)\n    if (!origin) return callback(null, true);\n\n    if (allowedOrigins.includes(origin)) {\n      callback(null, true);\n    } else {\n      callback(new Error('Not allowed by CORS'));\n    }\n  },\n  credentials: true,\n  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n  allowedHeaders: ['Content-Type', 'Authorization'],\n};\n\napp.use(cors(corsOptions));\n",[31,112,110],{"__ignoreMap":67},[36,114,116],{"id":115},"vercel-serverless-functions","Vercel Serverless Functions",[59,118,121],{"className":119,"code":120,"language":64},[62],"// api/data.ts\nimport type { VercelRequest, VercelResponse } from '@vercel/node';\n\nconst ALLOWED_ORIGINS = ['https://myapp.com'];\n\nexport default function handler(req: VercelRequest, res: VercelResponse) {\n  const origin = req.headers.origin as string;\n\n  if (ALLOWED_ORIGINS.includes(origin)) {\n    res.setHeader('Access-Control-Allow-Origin', origin);\n    res.setHeader('Access-Control-Allow-Credentials', 'true');\n  }\n\n  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\n\n  // Handle preflight\n  if (req.method === 'OPTIONS') {\n    return res.status(204).end();\n  }\n\n  // Your logic\n  return res.json({ message: 'Hello' });\n}\n",[31,122,120],{"__ignoreMap":67},[36,124,126],{"id":125},"understanding-preflight-requests","Understanding Preflight Requests",[18,128,129],{},"Browsers send an OPTIONS request before certain requests to check if the actual request is allowed. This happens when:",[131,132,133,137,140],"ul",{},[134,135,136],"li",{},"Using methods other than GET, HEAD, or POST",[134,138,139],{},"Sending custom headers (like Authorization)",[134,141,142],{},"Using Content-Type other than form data or text/plain",[18,144,145],{},"You must handle OPTIONS requests or your API calls will fail:",[59,147,150],{"className":148,"code":149,"language":64},[62],"// Handle preflight in any framework\nif (request.method === 'OPTIONS') {\n  return new Response(null, {\n    status: 204, // No Content\n    headers: corsHeaders,\n  });\n}\n",[31,151,149],{"__ignoreMap":67},[36,153,155],{"id":154},"when-you-dont-need-cors","When You Don't Need CORS",[18,157,158],{},"If your frontend and backend are on the same domain, you don't need CORS:",[131,160,161,168,174],{},[134,162,163,167],{},[164,165,166],"strong",{},"Same origin:"," Frontend at myapp.com, API at myapp.com/api",[134,169,170,173],{},[164,171,172],{},"Next.js API routes:"," Automatically same origin",[134,175,176,179],{},[164,177,178],{},"Server-side requests:"," CORS only applies to browsers",[36,181,183],{"id":182},"debugging-cors-issues","Debugging CORS Issues",[80,185,187],{"id":186},"check-the-error-message","Check the Error Message",[59,189,192],{"className":190,"code":191,"language":64},[62],"// Browser console errors and what they mean:\n\n// \"No 'Access-Control-Allow-Origin' header\"\n// -> Your server isn't returning the CORS headers at all\n\n// \"The value of the 'Access-Control-Allow-Origin' header...must not be '*' when credentials mode is 'include'\"\n// -> You're using credentials with wildcard origin\n\n// \"Method PUT is not allowed\"\n// -> Add PUT to Access-Control-Allow-Methods\n\n// \"Request header field authorization is not allowed\"\n// -> Add Authorization to Access-Control-Allow-Headers\n",[31,193,191],{"__ignoreMap":67},[80,195,197],{"id":196},"test-with-curl","Test with curl",[59,199,202],{"className":200,"code":201,"language":64},[62],"# Check what headers your server returns\ncurl -X OPTIONS https://api.myapp.com/data \\\n  -H \"Origin: https://myapp.com\" \\\n  -H \"Access-Control-Request-Method: POST\" \\\n  -v 2>&1 | grep -i \"access-control\"\n",[31,203,201],{"__ignoreMap":67},[36,205,207],{"id":206},"security-best-practices","Security Best Practices",[131,209,210,216,222,228,234,240],{},[134,211,212,215],{},[164,213,214],{},"Whitelist origins:"," Never use wildcards in production",[134,217,218,221],{},[164,219,220],{},"Validate origin server-side:"," Don't just echo back the Origin header",[134,223,224,227],{},[164,225,226],{},"Be specific with methods:"," Only allow methods you actually use",[134,229,230,233],{},[164,231,232],{},"Be specific with headers:"," Only allow headers you expect",[134,235,236,239],{},[164,237,238],{},"Consider same-origin:"," Use reverse proxy to avoid CORS entirely",[134,241,242,245],{},[164,243,244],{},"Use environment variables:"," Different allowed origins for dev/prod",[59,247,250],{"className":248,"code":249,"language":64},[62],"// Environment-based origin list\nconst ALLOWED_ORIGINS = process.env.NODE_ENV === 'production'\n  ? ['https://myapp.com']\n  : ['http://localhost:3000', 'http://localhost:3001'];\n",[31,251,249],{"__ignoreMap":67},[253,254,255,261,266],"related-articles",{},[256,257],"related-card",{"description":258,"href":259,"title":260},"Step-by-step guide to implementing TOTP-based two-factor authentication. Add 2FA with Google Authenticator, backup codes","/blog/how-to/two-factor-auth","How to Implement Two-Factor Authentication (2FA)",[256,262],{"description":263,"href":264,"title":265},"Step-by-step guide to validating user input. Zod schemas, server-side validation, common validation patterns, and why cl","/blog/how-to/validate-user-input","How to Validate User Input Securely",[256,267],{"description":268,"href":269,"title":270},"Step-by-step guide to setting up HashiCorp Vault for secrets management. Store, access, and rotate secrets securely in y","/blog/how-to/vault-basics","How to Use HashiCorp Vault for Secrets Management",{"title":67,"searchDepth":272,"depth":272,"links":273},2,[274,275,276,281,282,283,284,285,289],{"id":38,"depth":272,"text":39},{"id":53,"depth":272,"text":54},{"id":77,"depth":272,"text":78,"children":277},[278,280],{"id":82,"depth":279,"text":83},3,{"id":92,"depth":279,"text":93},{"id":102,"depth":272,"text":103},{"id":115,"depth":272,"text":116},{"id":125,"depth":272,"text":126},{"id":154,"depth":272,"text":155},{"id":182,"depth":272,"text":183,"children":286},[287,288],{"id":186,"depth":279,"text":187},{"id":196,"depth":279,"text":197},{"id":206,"depth":272,"text":207},"how-to","2026-01-23","Step-by-step guide to configuring CORS in Next.js, Express, and serverless functions. Avoid security mistakes and fix common CORS errors.",false,"md",null,"yellow",{},true,"Configure CORS correctly in Next.js, Express, and serverless functions.","/blog/how-to/setup-cors-properly","[object Object]","HowTo",{"title":5,"description":292},{"loc":300},"blog/how-to/setup-cors-properly",[],"summary_large_image","GHa1lZU_XQ9fN6ugAKualz_c25NIZNQCrMSFBhzsWkk",1775843918546]