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
- 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.
- 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).
- 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+).
- 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).
- 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.
- 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
- Audit every named import against CJS dependencies before enabling SSR; default-import or namespace-import CJS packages instead.
- Keep `optimizeDeps.include` populated for any CJS package your app imports by name so Vite pre-bundles ESM wrappers.
- Pin to ESM-native alternatives (lodash-es, rxjs >=7, etc.) and add a lint rule banning CJS-only package names from named imports.
- When publishing a CJS package that will be consumed by Vite, assign named exports one-by-one (`exports.foo = ...`) rather than `module.exports = { foo }` so cjs-module-lexer can detect them.
- Re-run the SSR build in CI with `optimizeDeps.exclude` empty; this surfaces CJS named-export mismatches before deploy.
- Use `import * as pkg from 'pkg'` during migration as a stopgap — it bypasses the check at ssrTransform.ts:35 and lets you destructure at runtime.
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
- config must export or return an object.
- Failed to resolve . This package is ESM only but it was…
- import.meta.resolve is not supported in CJS config files
- Cannot send non-custom events from the client to the server.
- Cannot send non-custom events from the server to the client.
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)