The require function is trying to load a module that was exported using export default, and it doesn’t know how to handle that.

This happens because Node.js has two module systems: CommonJS (CJS), which uses require() and module.exports, and ECMAScript Modules (ESM), which uses import and export. When you try to require() an ESM module, Node.js gets confused because the default export mechanism in ESM doesn’t directly map to how CJS expects to find exports.

Here are the common reasons this error pops up and how to fix them:

  1. Trying to require() an ESM module directly:

    • Diagnosis: You’ll see the error message when your CJS file attempts to require() a file that uses export default or top-level export.
    • Fix: If the module you’re importing is intended to be used in a CJS context, you need to ensure it’s also exported in a CJS-compatible way. This usually means the module author needs to provide a CJS build or you need to configure your build process (like Webpack or Rollup) to generate CJS output. If you control the module you’re importing, change its exports:
      // Original ESM export
      // export default { someValue: 123 };
      
      // CJS compatible export
      module.exports = { someValue: 123 };
      
      If you must require an ESM module, and it has a default export, you can often access it via a .default property:
      // In your CJS file
      const esmModule = require('your-esm-module');
      const value = esmModule.default; // Access the default export
      
    • Why it works: CJS expects exports to be properties of the module.exports object. When an ESM module uses export default, that default export is often attached to the exports object under the key default. Accessing it directly resolves the conflict.
  2. Incorrect type: "module" in package.json:

    • Diagnosis: Your package.json has "type": "module", which tells Node.js to treat all .js files as ESM by default. However, you might still have some files that are written in CJS syntax and are being incorrectly interpreted as ESM.
    • Fix: If you want your project to be primarily ESM, but have specific CJS files, rename those CJS files to have a .cjs extension. Node.js will then correctly interpret them as CommonJS.
      // package.json
      {
        "type": "module",
        // ...
      }
      
      # Rename your CJS file
      mv my-cjs-file.js my-cjs-file.cjs
      
    • Why it works: Explicitly naming files with .cjs overrides the global type: "module" setting for those specific files, ensuring they are parsed as CommonJS.
  3. Mixing import and require within the same file (and Node.js version issues):

    • Diagnosis: You’re using both import and require statements in a single file, and Node.js is getting confused about which module system it’s operating in. This is particularly problematic in older Node.js versions.
    • Fix: Avoid mixing import and require in the same file. If you need to import CJS modules from an ESM file, use dynamic import():
      // In your ESM file (e.g., with "type": "module")
      async function loadCjsModule() {
        const cjsModule = await import('your-cjs-module');
        // Use cjsModule.someExport
      }
      loadCjsModule();
      
      If you need to use ESM features in a CJS file, you generally can’t directly. You’d typically refactor the file to be ESM (by setting "type": "module" and using .js extensions, or using .mjs extensions) or use the .default access method if the ESM module is structured that way.
    • Why it works: Dynamic import() is the officially supported way to load CommonJS modules from an ECMAScript Module context. It handles the interop layer correctly.
  4. Third-party library not supporting ESM correctly:

    • Diagnosis: You’re trying to import a library that is published as ESM, but it’s not correctly exporting its modules or has internal CJS dependencies that aren’t handled well.
    • Fix: Check the library’s documentation for ESM support. It might require a specific import syntax, or it might have a separate CJS build you need to use. If the library is known to have issues, you might need to use a compatibility layer or find an alternative. For example, some libraries might export their main module under your-lib/dist/index.mjs or similar.
      // Potentially
      import { someFunction } from 'your-broken-esm-lib/dist/index.mjs';
      
    • Why it works: Explicitly pointing to the correct ESM entry point of the library, or using a version/build that is designed for ESM, bypasses the internal conflicts.
  5. Transpilation issues with Babel or TypeScript:

    • Diagnosis: Your build tool (like Babel or tsc) is configured to output CJS, but it’s encountering ESM syntax it can’t correctly transform, or vice-versa.
    • Fix: Ensure your tsconfig.json (for TypeScript) or .babelrc/babel.config.js (for Babel) is correctly configured for the target module system.
      • TypeScript: Set "module": "ESNext" (or similar ESM target) and "moduleResolution": "NodeNext" (or "Node16") if you’re building an ESM project. If you’re building CJS, use "module": "CommonJS" and "moduleResolution": "Node".
      // tsconfig.json for ESM output
      {
        "compilerOptions": {
          "target": "ESNext",
          "module": "ESNext",
          "moduleResolution": "NodeNext",
          // ... other options
        }
      }
      
      • Babel: Use presets like @babel/preset-env with appropriate modules option.
      // .babelrc for ESM output
      {
        "presets": [
          ["@babel/preset-env", { "modules": "esm" }]
        ]
      }
      
    • Why it works: Correct transpiler configuration ensures that the input module syntax is accurately converted to the desired output module format, resolving any internal inconsistencies.

After fixing these, you might encounter a "Cannot find module" error if your import paths are incorrect, especially when dealing with relative imports in ESM.

Want structured learning?

Take the full Nodejs course →