This error occurs when importing modules without file extensions in Vite projects. Node.js ESM requires explicit file extensions in all import statements, and Vite enforces this standard.
This error is thrown by Vite when you attempt to import a local module without specifying its file extension. Unlike bundlers like webpack that allow extensionless imports, Vite strictly follows Node.js ESM (ECMAScript Modules) specifications, which require full file paths including extensions. In Node.js ESM, relative import paths must include the complete file extension (e.g., `.js`, `.ts`, `.jsx`, `.tsx`, `.vue`, `.css`). This is a fundamental requirement of how ESM works in Node.js, not a Vite-specific limitation. The restriction ensures that module resolution is explicit and predictable. When Vite encounters an import statement like `import { helper } from './utils'` instead of `import { helper } from './utils.js'`, it cannot resolve the module and throws this error. This applies to all local file imports in your project, including components, utilities, and stylesheets.
Review your import statements and add the appropriate file extension to each local module import.
Before:
import MyComponent from './MyComponent'
import { helper } from '../utils/helpers'
import './styles.css'After:
import MyComponent from './MyComponent.jsx'
import { helper } from '../utils/helpers.js'
import './styles.css' // CSS imports already include extensionNote: Package imports (from node_modules) do not require extensions, only relative/absolute file paths do.
When importing TypeScript files in a Vite project, you have two options:
Option 1: Use .ts/.tsx extensions (recommended for Vite):
import { MyType } from './types.ts'
import MyComponent from './MyComponent.tsx'Option 2: Use .js/.jsx extensions (Node.js ESM standard):
// Import with .js even though source file is .ts
import { MyType } from './types.js'
import MyComponent from './MyComponent.jsx'While Node.js ESM technically expects .js extensions (since TypeScript compiles to JavaScript), Vite's dev server handles .ts/.tsx extensions natively, so using the actual source file extension is more straightforward.
Barrel files (index.ts/index.js) that re-export multiple modules are common sources of this error.
Before:
// src/components/index.js
export { default as Button } from './Button'
export { default as Input } from './Input'After:
// src/components/index.js
export { default as Button } from './Button.jsx'
export { default as Input } from './Input.jsx'Search your codebase for these patterns:
grep -r "from '\./" src/
grep -r 'from "\../' src/Update your tsconfig.json to work with ESM module resolution:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler", // Vite-compatible resolution
"allowImportingTsExtensions": true,
"noEmit": true // Vite handles compilation
}
}For Node.js 16+ compatibility in SSR scenarios:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node16" // or "nodenext"
}
}With node16 or nodenext, TypeScript will require .js extensions even for .ts source files.
If a third-party library in node_modules has extensionless imports in its ESM build, you have limited options:
Option 1: Use a CommonJS version (if available):
Check the library's package.json for a CommonJS export and configure Vite to use it:
// vite.config.js
export default {
resolve: {
alias: {
'problematic-lib': 'problematic-lib/dist/cjs/index.js'
}
}
}Option 2: Report the issue to the library maintainers - ESM builds should include file extensions per Node.js spec.
Option 3: Use optimizeDeps to pre-bundle the dependency:
// vite.config.js
export default {
optimizeDeps: {
include: ['problematic-lib']
}
}If you're using Vite for SSR (server-side rendering), ensure your package.json correctly declares the module type:
{
"type": "module"
}This tells Node.js to treat .js files as ESM. Without this field, Node.js defaults to CommonJS and will reject ESM syntax entirely.
If you need to mix CommonJS and ESM, use explicit file extensions:
- .mjs for ES modules
- .cjs for CommonJS modules
- .js behavior determined by package.json "type" field
Why does Node.js ESM require file extensions?
The ESM specification requires explicit file extensions to avoid ambiguity and filesystem operations during module resolution. Unlike CommonJS (which tries .js, .json, .node extensions automatically), ESM resolution must be deterministic and fast. This design decision eliminates the need for expensive filesystem lookups and makes module resolution predictable across different environments.
Webpack vs. Vite module resolution:
Webpack provides "magic" module resolution that tries multiple extensions and directory indexes automatically. Vite deliberately does not replicate this behavior in its dev server because it follows Node.js ESM semantics. However, Vite's build (using Rollup) does provide some automatic resolution. This inconsistency between dev and build can sometimes cause confusion.
TypeScript's .js extension requirement:
TypeScript expects you to write .js extensions in import statements even when importing .ts files, because the compiled output will be .js. This can feel counterintuitive in Vite, where you can import .ts files directly. The moduleResolution: "bundler" option tells TypeScript to accept Vite's behavior.
Performance implications:
Explicit file extensions significantly improve cold-start performance in development. Vite doesn't need to stat multiple file paths for each import, making the dev server extremely fast. This is one of the core reasons Vite is faster than webpack-based dev servers.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite