vitejs/vite · error · SyntaxError

[vite] Named export ' ' not found. The requested module ' '…

Error message

[vite] Named export '${lastBinding}' not found. The requested module '${rawId}' 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 '${rawId}';
const {${missingBindings.join(', ')}} = pkg;

What it means

This is a Vite SSR-only error thrown by analyzeImportedModDifference when a named import (e.g. `import { foo } from 'pkg'`) targets a CommonJS module whose `module.exports` does not statically expose that binding. Vite transforms SSR imports behind the scenes and, unlike Node.js, cannot always rely on cjs-module-lexer at runtime, so it manually emulates Node's native 'Named export not found' SyntaxError to surface the mismatch early. The guard fires only for non-ESM modules; for true ESM it throws the Node-style 'does not provide an export named' variant instead. It exists to fail fast in dev SSR rather than silently yielding `undefined` bindings.

Solutions

  1. Switch to the default-import pattern the error message itself suggests: `import pkg from 'pkg'; const { foo } = pkg;` — this sidesteps static named-export analysis entirely.
  2. Force the dependency through Vite's optimizer so named exports are pre-bundled as ESM: add it to `optimizeDeps.include` (and confirm `optimizeDeps.exclude` does not list it).
  3. If the package ships an ESM build, point your import at it explicitly (subpath or the `module`/`exports` entry), or upgrade to an ESM-native version (e.g. lodash-es, rxjs v7+).
  4. For SSR specifically, mark the package as non-external so Vite transforms it: add the name to `ssr.noExternal` (string, regex, or `true` to transform everything).
  5. If you control the CJS package, add named-export hints cjs-module-lexer can detect (`exports.foo = ...` or `Object.defineProperty(exports, 'foo', ...)`) instead of `module.exports = { foo }` assigned all at once.
  6. Use a namespace import `import * as pkg from 'pkg'` — the early-return at line 35 (`importedNames?.length` falsy / undefined for `import * as`) skips the check entirely.

Example fix

// before
import { debounce } from 'lodash'

// after (default import + destructure, as the error suggests)
import lodash from 'lodash'
const { debounce } = lodash

// or: force ESM pre-bundle in vite.config.ts
// export default defineConfig({
//   optimizeDeps: { include: ['lodash'] },
//   ssr: { noExternal: ['lodash'] }
// })

// or: use the ESM build
import { debounce } from 'lodash-es'
Defensive patterns

Strategy: validation

Validate before calling

// Before relying on a named import from a possibly-CJS dep in SSR,
// check whether Vite resolved it as ESM. Run in vite config or a plugin.
import { createServer } from 'vite'

const server = await createServer({ server: { middlewareMode: true } })
const id = await server.pluginContainer.resolveId('pkg-name')
if (id) {
  const mod = await server.ssrLoadModule('pkg-name').catch(() => null)
  // introspect: is the binding actually exported?
  const ok = mod != null && 'theBinding' in mod
  if (!ok) {
    // fall back to default import or pre-bundle via optimizeDeps.include
  }
}
await server.close()

// Cheaper static check: read the dep's package.json before adding the import
import { readFileSync } from 'node:fs'
const pkg = JSON.parse(readFileSync('node_modules/pkg-name/package.json', 'utf8'))
const isEsm = pkg.type === 'module' || (pkg.exports && typeof pkg.exports === 'object')
// if !isEsm, prefer `import pkg from 'pkg-name'` over named imports

Type guard

// Narrow a dynamically-imported CJS module to a known shape before destructuring.
import type { ExpectedNamedExports } from './types'

function hasNamedExports(
  mod: unknown,
  names: readonly (keyof ExpectedNamedExports)[],
): mod is ExpectedNamedExports {
  if (typeof mod !== 'object' || mod === null) return false
  return names.every((n) => n in mod)
}

// usage in SSR code that cannot statically prove ESM:
// const raw = await import('maybe-cjs-pkg')
// const mod = (raw as any).default ?? raw
// if (!hasNamedExports(mod, ['foo', 'bar'] as const)) {
//   throw new Error('maybe-cjs-pkg is missing expected named exports')
// }

Try / catch

// This is a SyntaxError thrown synchronously during SSR module load;
// catching it at the call site is rarely useful — it means the import
// is fundamentally broken. Prefer validation (above) or fixing the import.
// If you must guard a dynamic SSR pipeline, catch around ssrLoadModule:
try {
  const mod = await viteServer.ssrLoadModule('./src/uses-cjs-pkg.ts')
} catch (err) {
  if (err instanceof SyntaxError && /Named export .* not found/.test(err.message)) {
    // log + surface a friendly 'switch to default import' hint to the user
    throw new Error('Configure optimizeDeps.include or use default import for the failing package')
  }
  throw err
}

Prevention

When it happens

Trigger: Triggered when `metadata.importedNames` is a non-empty array (i.e. the user wrote named imports, not `import * as` or a bare side-effect import), the resolved module is NOT flagged `moduleType === 'module'` (it is CJS), and `metadata.importedNames.filter((s) => !(s in mod))` returns at least one missing binding. Concretely: `import { debounce } from 'lodash'` (CJS lodash, no static named export detected), `import { Provider } from 'some-cjs-pkg'` where the package assigns `module.exports = { Provider }` but isn't analyzed, or any deep named import from a `.cjs`/UMD package under `ssr` mode. Dynamic imports (`import()`) are exempt: the function returns early when `metadata?.isDynamicImport` is true.

Common situations: Most common after enabling SSR (or adding `ssr.noExternal: false`) on a project that imports legacy CJS/UMD libraries via named imports — lodash, moment, classnames, qs, rxjs pre-v7, older react-redux, internal company packages shipped as CJS. Also surfaces after upgrading Vite/Rollup across versions where cjs-module-lexer detection regressed, after adding `optimizeDeps.exclude` for a CJS dep, or when a package.json `exports`/`type` field flips a module between ESM and CJS. Misconfigured `ssr.external` or pointing a named import at a package whose `main` field points at a CJS build while `module`/`exports` point at ESM is a frequent cause.

Related errors


AI-assisted analysis of vitejs/vite@b4d66fee14 (2026-08-11). Data as JSON: /api/errors/93ae144094696b8f. Report an issue: GitHub.

Appendix: source

Thrown at packages/vite/src/shared/ssrTransform.ts:49

  // If the user named imports a specifier that can't be analyzed, error.
  // If the module doesn't import anything explicitly, e.g. `import 'foo'` or
  // `import * as foo from 'foo'`, we can skip.
  if (metadata?.importedNames?.length) {
    const missingBindings = metadata.importedNames.filter((s) => !(s in mod))
    if (missingBindings.length) {
      const lastBinding = missingBindings[missingBindings.length - 1]

      // For invalid named exports only, similar to how Node.js errors for top-level imports.
      // But since we transform as dynamic imports, we need to emulate the error manually.
      if (moduleType === 'module') {
        throw new SyntaxError(
          `[vite] The requested module '${rawId}' does not provide an export named '${lastBinding}'`,
        )
      } else {
        // For non-ESM, named imports is done via static analysis with cjs-module-lexer in Node.js.
        // Copied from Node.js
        throw new SyntaxError(`\
[vite] Named export '${lastBinding}' not found. The requested module '${rawId}' 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 '${rawId}';
const {${missingBindings.join(', ')}} = pkg;
`)
      }
    }
  }
}

View on GitHub (pinned to b4d66fee14)