Skip to content

Instantly share code, notes, and snippets.

@AndrewIngram
Last active January 20, 2023 21:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AndrewIngram/7462b597d1dde0adf5c10e3eadaae564 to your computer and use it in GitHub Desktop.
Save AndrewIngram/7462b597d1dde0adf5c10e3eadaae564 to your computer and use it in GitHub Desktop.
MASSIVE HACK ALERT: Using headers() and cache() in Next 13.1 API routes

Warning, this is a bad idea. It'll eventually break and use undocumented framework internals

Using headers() and cache() in Next 13.1 API routes

Note: API routes are still only supported in the pages folder, not app.

I was playing around with using API routes with the experimental app dir stuff in Next 13, and I was frustrated at having to use completely different code paths for accessing the request headers and cookies compared to in server components. So I hacked together a workaround which you definitely shouldn't use.

Usage requires two steps, one is easy, the other is invasive:

  1. Wrap any API routes with the withRequestContext function defined in this gist
  2. Make sure any use of headers() and cookies() in the code paths of these API routes only uses local imports for these functions. Module-level imports will erroneously trigger some logic in Next whereby it thinks a client component is being rendered, and throws an exception which I haven't been able to suppress without something like patch-package:

So instead of:

import { cookies, headers } from "next/headers";

export function myDataLoadingThing() {
   // do something with cookies() or headers()
}

Do:

export async function myDataLoadingThing() {
  const { cookies, headers } = await import("next/headers");

   // do something with cookies() or headers()
}
import type { NextApiRequest, NextApiResponse } from "next";
import { requestAsyncStorage } from "next/dist/client/components/request-async-storage";
import type { RenderOpts } from "next/dist/server/render";
import { runWithRequestAsyncStorage } from "next/dist/server/run-with-request-async-storage";
// Wrapper for API route handlers that sets up the request context
export function withRequestContext(
fn: (req: NextApiRequest, res: NextApiResponse) => Promise<unknown> | unknown,
) {
return async function (req: NextApiRequest, res: NextApiResponse) {
return new Promise((resolve) => {
runWithRequestAsyncStorage(
requestAsyncStorage,
{ req, res, renderOpts: {} as RenderOpts },
async () => {
const result = await fn(req, res);
resolve(result);
},
);
});
};
}
import type { NextApiRequest, NextApiResponse } from "next";
import { getAddressesForPostcode } from "~/api";
import { withRequestContext } from "~/shims";
export default withRequestContext(
async (
req: NextApiRequest,
res: NextApiResponse<
Awaited<ReturnType<typeof getAddressesForPostcode>> | { error: string }
>,
) => {
const { postcode } = req.query;
if (!postcode) {
return res.status(400).json({ error: "Postcode required" });
}
// `getAddressesForPostcode` can use cache() or headers() internally
const addresses = await getAddressesForPostcode(postcode as string);
return res.status(200).json(addresses);
},
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment