Content Headers from NextJS route handlers

I am attempting to create a Vercel deployment of a NextJS v14 project, using the “app router” method of internal routing. I created a few pages that handle responses directly, which are attempting to fulfil the requirements to be an ActivityPub server. Part of that requires that the Content-Type header for JSON-style responses to be set to application/ld+json; profile="https://www.w3.org/ns/activitystreams".

Using a sample script of api/instance-actor/route.ts

export async function GET(request: NextRequest) {
  return new Response(JSON.stringify({ hello: 'world' }), {
    headers: {
      'Content-Type':
        'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
    },
  })
}

that returns a proper response when I test with Postman against a local NextJS dev build.

However, when it gets deployed to Vercel, even with the Vercel toolbar turned off, there appears to be some security screen served up instead of the actual content. Using Postman to get at the plain response, when fetching api/instance-actor/, Vercel is returning a text/html document with a title of “Authentication Required”.

This appears to be a security screen that works well in a browser to pass the visitor along, but not for a machine-to-machine call.

How can I designate specific routes within my NextJS application to be intended for machine-to-machine communication, and therefore needs CORS and other settings configured to not require a browser-style challenge to be passed?

Can you try using the Next.js Script component next/script. You will likely also need to use dangerouslySetInnerHTML:

import Script from "next/script";

<Head>
    <Script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonData) }}
    />
</Head>
1 Like

This seems to be not the desired effect, @swarnava . What you’re describing here is embedding a <script> tag into a larger HTML document (a NextJS page.tsx file. Having a <Script> component inside a <Head> throws a linting error, but updating your code to be

import Script from 'next/script'

export default function TestPage() {
  return (
      <Script
        type="application/ld+json"
        id="mydata"
        dangerouslySetInnerHTML={{ __html: JSON.stringify({ hello: 'world' }) }}
      />
  )
}

The result I get is a JSON-LD block embedded in an HTML page. That is a way to combine human-friendly HTML with JSON-LD data for some situations

But for this use-case, I need it to not have the outer HTML wrapper, as other ActivityPub nodes require the overall document being fetched to have a specific Content-Type (which is why I’m using a route.ts instead of a page.tsx to define it).

I created this application’s Vercel deploy by just designating it’s a NextJS application. However, taking a look at the documentation for Vercel functions, I wonder if possibly Vercel is incorrectly identifying my route.ts files, because I have them nested in src/app/api/ rather than in app/api/. Is there a separate config I need to set to indicate I use the /src folder to separate source files from output files?

Pinging this to the top of the list. Is there a way to define a Vercel NextJS deploy to use /src/api as the endpoints that should be treated as back-end, rather than just /api /src/app/api as the endpoints that should be treated as back-end, rather than just /app/api?

Hi, @midnightlightning! Thanks for your patience :pray:

Pinging this to the top of the list. Is there a way to define a Vercel NextJS deploy to use /src/api as the endpoints that should be treated as back-end, rather than just /api?

Let me address the question about using /src/api as the backend endpoints in a Next.js project deployed on Vercel.

In Next.js, especially when using the App Router (which is the default in Next.js 13 and later), the standard location for API routes is within the app directory. However, you can structure your project to use /src/api, though it requires some additional configuration.

I’m assuming that your project structure looks something like this:

your-project/
├── src/
│   ├── api/
│   │   └── your-api-routes.ts
│   └── app/
│       └── your-app-routes.tsx
├── next.config.js
└── package.json

You’ll need to modify your next.config.js file to redirect API requests to your src/api directory that tells Next.js to rewrite requests from /api/* to /src/api/*:

Code Example
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: '/src/api/:path*',
      },
    ];
  },
};

module.exports = nextConfig;

In your src/api directory, create your API routes as you normally would. For example:

// src/api/hello/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({ message: 'Hello from /src/api!' });
}

When you deploy to Vercel, it will automatically detect your Next.js project and use your next.config.js settings. Your API routes should be accessible at https://your-domain.vercel.app/api/your-route, even though they’re located in the src/api directory in your project.

It’s important to note that while this setup will work, it’s not the standard convention for Next.js projects. The recommended approach is to keep your API routes within the app directory when using the App Router. This aligns with Next.js best practices and requires less configuration.

If you’re using this structure for a specific reason (like separating frontend and backend code more distinctly), it’s valid, but be aware that it might be less intuitive for developers familiar with standard Next.js conventions.

Remember, when using the App Router, your API routes are created as Route Handlers. Whether in src/api or app/api, they should be defined in route.ts files.

Does that help?

Thanks @pawlean; the application I was attempting to deploy is this one. It’s using NextJS v14, and you’re right, I misspoke: the source code is in /src/app/api defined as route.ts files to be “back-end”/API endpoints.

The key file I’m trying to make work is saved in /src/app/api/activitypub-fetch/route.ts and it’s attempting to fulfil the requirements for WebFinger requests. So there is a redirect configured to redirect /.well-known/webfinger to that route.

I need that route to be able to return a specific Content-Type header, but it’s being intercepted and modified by Vercel.

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