If you’re spinning up a simple API endpoint without a front end, you likely wouldn’t want to use a service like Vercel,
Netlify
, or similar and should instead spin up a lambda function directly on AWS or another cloud provider, but for the sake of this example, it’s much easier to get it up and running using Vercel or Netlify and focus on CORS.
Step 0: Creating a New API Route with Vercel
To get started, we’ll need to set up a new API route.
You don’t need to use Vercel, but it’s what I’ll be using, so if you want to follow along exactly with the examples, you can either use a
starter that I created
to make it easy to set up or add a new API route to your Next.js (or similar) project.
export async function GET(request: Request) {
const results = await fetch('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY').then(r => r.json());
return new Response(JSON.stringify(results));
const response = await fetch(`http://localhost:3000/api/image`)
const data = await response.json()
console.log('data', data)
You’ll immediately see an error talking about CORS.
return new Response(JSON.stringify(results), {
headers: {
'Access-Control-Allow-Origin': '<Your Origin (Ex: https://spacejelly.dev)>',
const ALLOWED_ORIGINS = [
`https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`,
'https://colbyfayock.com',
'https://spacejelly.dev'
Now as far as testing this, when using your local environment with the Vercel CLI, this value will be undefined, but when deployed to production, we’ll see that the “allowed” header returned is our production environment!
const results = await fetch('http://localhost:3000/api/image', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}).then(r => r.json());
console.log('results', results);
We’ll immediately see it fail.
export function OPTIONS(request: Request) {
const requestOrigin = request.headers.get('origin');
const accessOrigin = ALLOWED_ORIGINS.find(origin => origin === requestOrigin) || ALLOWED_ORIGINS[0];
return new Response(JSON.stringify({ status: 'Ok' }), {
status: 200,
headers: {
'Access-Control-Allow-Origin': accessOrigin,
This is basically the same request as our POST request (formerly GET in my example), except instead of a dynamic response, we’re setting a static response (this value doesn’t matter).
If we try to now make a request to our endpoint, our request will still fail!
But it might fail for different reasons…
First off, unfortunately it looks like at the time of writing this, the Vercel CLI has an issue with OPTIONS requests, but if you’re not using Vercel, you may not run into this issue.
But to test this out, you can push the code to a preview branch or production deployment, make the test request to that deployed endpoint, and the request should now pass!
Once that’s resolved, or if you’re not using the Vercel CLI, we’ll now get an error that the Content-Type
header is not allowed. We now need to explicitly configure this header in our response!
Similar to the origin, we can either set the header name, a *
, or with this header, we can set multiple names.
In our case, we just need Content-Type, so add the following to both the OPTIONS and POST request:
'Access-Control-Allow-Headers': 'Content-Type',
And now, if we try to make the request again, it should work and get the response!