Vercel Functions - Content-Type header in vercel.json not working

Hello,

I’m finding that the Content-Type header I set in vercel.json for my Serverless Function is not actually being set on my response. The other headers I define here are (Cache-Control, Access-Control), but for some reason Content-Type is being ignored and the default text/plain;charset=UTF-8 is being applied.

Setting the Content-Type header when returning a response from the function body works fine, but since all of my endpoints will return the same content type, I would rather configure this in vercel.json.

Is this possible?

What doesnt work:

// api/helloWorld.ts
export function GET(request: Request) {
  return new Response(JSON.stringify({hello: 'world'}));
}

// vercel.json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "version": 2,
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Content-Type", "value": "application/json" }, // ignored
        { "key": "Cache-Control", "value": "public, s-maxage=604800000" }, // applied
        { "key": "Access-Control-Allow-Credentials", "value": "true" } // applied
      ]
    }
  ]
}

What does work:

// api/helloWorld.ts
export function GET(request: Request) {
  return new Response(JSON.stringify({hello: 'world'}), {
    headers:[
      ['Content-Type', 'application/json'] // applied
    ]
  });
}

// vercel.json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "version": 2,
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, s-maxage=604800000" }, // applied
        { "key": "Access-Control-Allow-Credentials", "value": "true" } // applied
      ]
    }
  ]
}

This is because by default response returns have a Content-Type set, so that overrides your top level content type. a default function response on Vercel returns text/plain you can always just make a custom response that’s just the base response + the json header like so

export class JsonResponse extends Response {
  constructor(body: any, init?: ResponseInit) {
    super(JSON.stringify(body), {
      ...init,
      headers: { ...init?.headers, "Content-Type": "application/json" },
    });
  }
}

This can replace it to

export function GET(request: Request) {
  return new JsonResponse({ hello: "world" });
}

this automatically handles encoding the input as JSON and setting the header, if all your responses are JSON this is probably the best approach

Also please forgive the sin of JS OOP it’s just the easiest way to do this nicely

It also allows for typing the function returns still as Response due to the extension

import { JsonResponse } from "../util";

export function GET(request: Request): Response {
  const name: string = new URL(request.url).searchParams.get("name") ?? "";
  return new JsonResponse({ message: `Hello ${name}!` });
}
2 Likes

Hi, @devklick!

Welcome to the Vercel Community :smiley:

@versecafe’s answer is spot on AFAIK. Thanks for helping out, @versecafe!

1 Like

Thanks for a clear explanation and a clear suggested solution :smile:

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.