5

I'm wondering if the following scenario is possible with serverless (deployed on Vercel) Next JS:

I have a route /product/[id].tsx when you send a request with the header Accept: text/html, I would like it to go through the normal Next JS flow with a React page. But, when a request is given with Accept: application/json, I would like it to return the JSON representation.

I have accomplished it by using some custom middleware I wrote for Express (see below), but I would also like to deploy it on Vercel, and Vercel is not designed to work with a custom Express implementation.

So the question is, is it possible to do this in a serverless environment? Could one of the Next JS React pages return pure JSON, or could you return React from a Next JS Api route? Or is there another way to accomplish this?

server.ts

import express, { Request, Response } from "express";
import next from 'next'
import { parse } from 'url'

const port = parseInt(process.env.PORT || "3000", 10);
const dev = process.env.NODE_ENV !== "production";

async function run(): Promise<void> {
  const app = express();
  const nextApp = next({ dev })
  const nextHandler = nextApp.getRequestHandler()
  await nextApp.prepare()

  app.use((req, res) => {
    res.format({
      "text/html": async (req, res) => {
        const parsedUrl = parse(req.url!, true)
        await this.handler(req, res, parsedUrl)
      },
      "application/json": async (req, res) => {
        res.json({
          "dummy": "data"
        })
      }
    })
  });

  app.listen(port, () => {
    console.log(
      `> Server listening at http://localhost:${port} as ${
        dev ? "development" : process.env.NODE_ENV
      }`
    );
  });
}

run()
jaxoncreed
  • 1,069
  • 2
  • 13
  • 24

1 Answers1

8

Thanks to @timneutkens (Lead engineer for NextJS) on Twitter we have our answer. It is possible. You use getServerSideProps and perform everything you want to the res object there. Just be sure to end it with .end so it skips the react rendering phase.

I've made a sample repo for the code here: https://github.com/jaxoncreed/nextjs-content-type-test

And here's the page that matters:

import Head from 'next/head'

export default function Home() {
  return (
    <div className="container">
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <h1>Test App</h1>
      </main>

      
    </div>
  )
}

export async function getServerSideProps({ req, res }) {
  if (req.headers.accept === "application/json") {
    res.setHeader('Content-Type', 'application/json')
    res.write(JSON.stringify({ "dummy": "data" }))
    res.end()
  }
  return {
    props: {}, // will be passed to the page component as props
  }
}
jaxoncreed
  • 1,069
  • 2
  • 13
  • 24
  • 1
    This is phenomenal. I was able to use this with the `feed` package to make a fully custom RSS feed. Nice trick. – mlissner Aug 04 '21 at 01:18