next.js: Nextjs fails to detect ESM modules correctly when using exports in package.json

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 21.2.0: Sun Nov 28 20:28:41 PST 2021; root:xnu-8019.61.5~1/RELEASE_ARM64_T6000
Binaries:
  Node: 16.16.0
  npm: 8.11.0
  Yarn: 1.22.15
  pnpm: 7.5.2
Relevant packages:
  next: 12.2.4
  eslint-config-next: 12.2.4
  react: 18.2.0
  react-dom: 18.2.0

What browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

Describe the Bug

Next.js fails to resolve sub dependencies correctly and tries to load CJS from ESM modules, causing the build to fail.

SyntaxError: Named export 'theme' not found. The requested module '@chakra-ui/react' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

@chakra-ui/react a dependency of @saas-ui/react

Saas UI uses exports definition in package.json to define package entries and exports the ESM module as default.

{
  "exports": {
    ".": {
      "require": "./dist/index.js",
      "default": "./dist/index.modern.mjs"
    },
    "./src": {
      "default": "./src/index.ts"
    }
  },
  "main": "./dist/index.js",
  "module": "./dist/index.modern.mjs",
  "types": "./dist/index.d.ts"
}

Removing exports or changing default to import fixes the issue.

However I think Next.js should handle this correctly.

Expected Behavior

Next.js resolves all packages correctly as ESM.

Link to reproduction

https://github.com/msnegurski/test-saas-ui

To Reproduce

  1. Clone the repo.
  2. npm i
  3. npm run dev

Make sure @saas-ui/react@1.2.x is installed (not 1.3)

NEXT-1381

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 38
  • Comments: 26 (6 by maintainers)

Commits related to this issue

Most upvoted comments

Alright. I’ve researched this for quite some while now. What I reported above seems to hold true.

Now for people searching for a SOLUTION only I have exactly 2 proposals for you:

  1. Clean one: Go to the affected repository of the “problematic” library and make sure they export the ESM files with .mjs extension and/or have it set type: "module" in the package.json

  2. Workaround: Abuse your next.config.js:

const nextConfig = {
 experimental: {
    transpilePackages: ["problematic-package", "other-problematic-package", "etc", "..."],
  },
}

Warning: This isn’t a clean solution so only do this when you are beyond desperate.

https://github.com/vercel/next.js/issues/39375#issuecomment-1208109442 So if no .mjs are found (even though type = module , or ‘import’ is defined, it will always treat the package as cjs?

Our research would confirm this. We have the following setup:

NextJS project that uses @our-company/lib

@our-company/lib uses @tabler/icons as dependency (and FYI, even if it’s not a dependency but a peerDependency NextJS will act the same way).

@tabler/icons correctly defines this in it’s package.json:

{
 "exports": { ".": { "import": "./icons-react/dist/index.esm.js" } }
}

Now the above ends with .js and not with .mjs. Technically this is perfectly fine.

Now what NextJS does is: If THAT package is requested from ANOTHER package (so not directly from the NextJS app but from a sub-package, in our case @our-company/lib) it will ALWAYS throw the error that the ESM file is a CJS file, probably judging by its extension .js instead of .mjs .

This is extremely weird and I’d be willing to help out here if I know in which file to start. I am actually wondering why so few people seem to have this issue. But I assume that people find workarounds.

Our findings are backed by the fact that if we rename those files to .mjs by hand it works.

Fixed it with those two steps

  1. [OPTIONAL] Change tsconfig.json, from node to node16 (moduleResolution)
    "module": "esnext",
    "moduleResolution": "node16",
    "resolveJsonModule": true,

Looks like this step isn’t needed. but feel free to change it if you like.

  1. Follow instructions here to fix the issue: https://nextjs.org/docs/messages/import-esm-externals

  2. Add transpilePackages in next.config.mjs

  transpilePackages: ["@nivo/line", "@nivo/colors", "d3-color"],

I’m on next 13.1.2

Is tsconfig.js make sure moduleResolution is set to node16

Too few information: When does this help and where? Are you suggesting to set moduleResolution: node16 in the project (NextJS project) that is using troublesome (non-mjs peerdeps) libraries?

Facing the same issue when bumping nextjs from 13.2.4 to 13.3.1 on liveReload.

It still works in 13.2.4, but breaks as soon I bump to 13.3.1.

error - file:///Users/marco/code/blog/node_modules/next-contentlayer/dist/hooks/useLiveReload.js:1
import { addMessageListener } from 'next/dist/client/dev/error-overlay/websocket.js';
         ^^^^^^^^^^^^^^^^^^
SyntaxError: Named export 'addMessageListener' not found. The requested module 'next/dist/client/dev/error-overlay/websocket.js' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'next/dist/client/dev/error-overlay/websocket.js';
const { addMessageListener } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:190:5) {
  digest: undefined
}

Any thoughts?

  • I don’t have any exports in my own package.json
  • The latest canary installed at 26-04-2023 20:12 CET still has the same bug.

Not sure yet, will have to investigate further.

So if no .mjs are found (even though type = module , or ‘import’ is defined, it will always treat the package as cjs?

Ok, my bad I misread this yesterday.

Then we can agree using both ‘import’ and ‘require’ is the way to go, leaves us only with the question why the compiler behaves different with .js and .mjs file extensions for es modules.

Thanks @balazsorban44

This issue is related to: https://github.com/chakra-ui/chakra-ui/issues/6436

Chakra UI uses .js extension for ESM modules. Any other package that depends on Chakra UI and uses .mjs extension for ESM modules will run into this error. Renaming all .mjs files to .js in @saas-ui/react ultimately resolved it for now, but this is more a workaround then a solution imho.

I looked into framer as well, which had a similar issue open with Next.js last year. They are using require (.js) and default (.mjs) without any issues, so that doesn’t seem to be the problem.

https://github.com/vercel/next.js/issues/30750

Here to proposed solution was to use .mjs.

I’m not sure what is going on, but anyway the compiler behaviour changes based on which extension is used for ESM modules, while the package.json definitions are the same, as well as the code of the modules.