hoangvvo / next-connect

The TypeScript-ready, minimal router and middleware layer for Next.js, Micro, Vercel, or Node.js http/http2
https://www.npmjs.com/package/next-connect
MIT License
1.62k stars 65 forks source link

Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization #189

Closed brianlovin closed 2 years ago

brianlovin commented 2 years ago

Hey team! I'm working through adding next-connect to all of my API pages in a Next.js project. I'm using the factory pattern that I've seen outlined in other issues:

import nc from 'next-connect';

export default function nextConnectHandler() {
  return nc<NextApiRequest, NextApiResponse>({
    onError(error: Error, req: NextApiRequest, res: NextApiResponse) {
      console.log({ error });
      res.status(501).json({ error: `${error.message}` });
    },
    onNoMatch(req: NextApiRequest, res: NextApiResponse) {
      res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
    },
  });
}

Then, from an API route, I can call it like this:

// /api/foo.ts
import nextConnectHandler from 'utils/nextConnectHandler';

const apiRoute = nextConnectHandler();

apiRoute.get(async (req, res) => {
  // some logic
});

export default apiRoute;

On the client, results are sporadic. Sometimes my API requests resolve just fine, but other times I'm getting waves of this error:

Screen Shot 2022-05-16 at 22 37 20@2x
error - (api)/utils/nextConnectHandler.ts (23:44) @ nextConnectHandler
TypeError: (0 , next_connect__WEBPACK_IMPORTED_MODULE_1__.default) is not a function

Have you encountered something like this before? Googling and issue search on this repo didn't turn up anything quite like it.

I have also tried:

Interestingly, I can get some of my pages to load and fetch data (correctly!) when server rendered. But as soon as the page renders and the React hydrates, I have some swr calls to my API that are failing with the above error.

Here are my versions:

"next-connect": "^0.12.2",
"next": "^12.1.6",
hoangvvo commented 2 years ago

So this is the CJS export of next-connect: https://unpkg.com/next-connect@0.12.2/dist/index.cjs

This is the ESM version: https://unpkg.com/next-connect@0.12.2/dist/index.js

It seems that webpack is trying to pull the CJS version which has the export written as:

module.exports = function

(So no .default - which is technically inaccurate according to ESM spec). When we write import nc from "next-connect" and transpile that, it would become const nc = require("next-connect").default; which undefined considering the module.exports above.

Normally, you would enable esModuleInterop so that TS will transform the import code (import nc from "next-connect") to something like:

__importDefault(require("next-connect")); // automatically handle the discrepency

and it would work.

However, if it actually pulls the ESM version, it should not be a problem, so I am not sure why this is not the case.

I will look into this asap.

hoangvvo commented 2 years ago

Hey @brianlovin, could you please give me some more info on your usage?

I definitely saw the issue above (not in next-connect but other packages with default export and in CommonJS) so my guess in the previous comment could be the case.

However, I just spin up a Next.js project but could not reproduce it. (I believe it's valid though, just want a way to reproduce to come up w a solution)

Thanks!

brianlovin commented 2 years ago

Thanks for investigating this @hoangvvo —

I was using this at the root level of each of my API pages. Here's loosely what I did:

1. I started with a nextConnect instance per file:

import type { NextApiRequest, NextApiResponse } from 'next';
import Cors from 'cors';
import { prisma } from '~/web/lib/prisma';
import nextConnect from 'next-connect';
import { User } from '@prisma/client';
import { getToken } from 'next-auth/jwt';

async function getUser(email: string): Promise<User | null> {
  return await prisma.user.findUnique({
    where: { email },
  });
}

const apiRoute = nextConnect<NextApiRequest, NextApiResponse>({
  onError(error, req, res) {
    res.status(501).json({ error: `Error while validating Figma token: ${error.message}` });
  },
  onNoMatch(req, res) {
    res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
  },
});

apiRoute.use(Cors());

apiRoute.get(async (req, res) => {
  const token = await getToken({ req });
  const data = await getUser(token.email)
  return res.status(200).json({ status: 'ok', data });
});

export default apiRoute;

2. I abstracted out the apiRoute constructor to a new file

import nextConnect from 'next-connect'

export default function createApiRoute() {
  return nextConnect<NextApiRequest, NextApiResponse>({
    onError(error, req, res) {
      res.status(501).json({ error: `Error while validating Figma token: ${error.message}` });
    },
    onNoMatch(req, res) {
      res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
    },
   });
}

3. Then I imported that factory for apiRoutes into each of my API pages.

// ...
import nc from 'helpers/createNextConnect'

async function getUser(email: string): Promise<User | null> {
  return await prisma.user.findUnique({
    where: { email },
  });
}

const apiRoute = nc()

apiRoute.use(Cors());

apiRoute.get(async (req, res) => {
  const token = await getToken({ req });
  const data = await getUser(token.email)
  return res.status(200).json({ status: 'ok', data });
});

export default apiRoute;

That's where the errors above started happening. Hope this makes sense.

hoangvvo commented 2 years ago

Thanks for investigating this @hoangvvo —

I was using this at the root level of each of my API pages. Here's loosely what I did:

1. I started with a nextConnect instance per file:

import type { NextApiRequest, NextApiResponse } from 'next';
import Cors from 'cors';
import { prisma } from '~/web/lib/prisma';
import nextConnect from 'next-connect';
import { User } from '@prisma/client';
import { getToken } from 'next-auth/jwt';

async function getUser(email: string): Promise<User | null> {
  return await prisma.user.findUnique({
    where: { email },
  });
}

const apiRoute = nextConnect<NextApiRequest, NextApiResponse>({
  onError(error, req, res) {
    res.status(501).json({ error: `Error while validating Figma token: ${error.message}` });
  },
  onNoMatch(req, res) {
    res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
  },
});

apiRoute.use(Cors());

apiRoute.get(async (req, res) => {
  const token = await getToken({ req });
  const data = await getUser(token.email)
  return res.status(200).json({ status: 'ok', data });
});

export default apiRoute;

2. I abstracted out the apiRoute constructor to a new file

import nextConnect from 'next-connect'

export default function createApiRoute() {
  return nextConnect<NextApiRequest, NextApiResponse>({
    onError(error, req, res) {
      res.status(501).json({ error: `Error while validating Figma token: ${error.message}` });
    },
    onNoMatch(req, res) {
      res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
    },
   });
}

3. Then I imported that factory for apiRoutes into each of my API pages.

That's where the errors above started happening. Hope this makes sense.

Thanks @brianlovin! Could you also include your TypeScript Config? I feel like it might have something to do with esModuleInterop option. Also with your next version too would be great! (just saw it in your first issue)

brianlovin commented 2 years ago

Sure! This is mostly the Next.js default:

{
    "compilerOptions": {
        "target": "es5",
        "lib": ["dom", "dom.iterable", "esnext"],
        "allowJs": true,
        "skipLibCheck": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noEmit": true,
        "esModuleInterop": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "jsx": "preserve",
        "incremental": true,
        "baseUrl": ".",
        "paths": {
            "~/*": ["../*"]
        }
    },
    "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "prisma/seed.js", "prisma/seedData.js"],
    "exclude": ["node_modules"]
}