docs/guide/common-errors.md
If you receive an error that module cannot be found, it might mean several different things:
You misspelled the path. Make sure the path is correct.
It's possible that you rely on baseUrl in your tsconfig.json. Vite doesn't take into account tsconfig.json by default, so you might need to install vite-tsconfig-paths yourself, if you rely on this behavior.
import { defineConfig } from 'vitest/config'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths()]
})
Or rewrite your path to not be relative to root:
- import helpers from 'src/helpers'
+ import helpers from '../src/helpers'
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
alias: {
'@/': './src/', // [!code --]
'@/': new URL('./src/', import.meta.url).pathname, // [!code ++]
}
}
})
This error can happen when NodeJS's fetch is used with pool: 'threads'. See #3077 for details.
The default pool: 'forks' does not have this issue. If you've explicitly set pool: 'threads', switching back to 'forks' or using 'vmForks' will resolve it.
If you are using custom conditions in your package.json exports or subpath imports, you may find that Vitest does not respect these conditions by default.
For example, if you have the following in your package.json:
{
"exports": {
".": {
"custom": "./lib/custom.js",
"import": "./lib/index.js"
}
},
"imports": {
"#internal": {
"custom": "./src/internal.js",
"default": "./lib/internal.js"
}
}
}
By default, Vitest will only use the import and default conditions. To make Vitest respect custom conditions, you need to configure ssr.resolve.conditions in your Vitest config:
import { defineConfig } from 'vitest/config'
export default defineConfig({
ssr: {
resolve: {
conditions: ['custom', 'import', 'default'],
},
},
})
::: tip Why ssr.resolve.conditions and not resolve.conditions?
Vitest follows Vite's configuration convention:
resolve.conditions applies to Vite's client environment, which corresponds to Vitest's browser mode, jsdom, happy-dom, or custom environments with viteEnvironment: 'client'.ssr.resolve.conditions applies to Vite's ssr environment, which corresponds to Vitest's node environment or custom environments with viteEnvironment: 'ssr'.Since Vitest defaults to the node environment (which uses viteEnvironment: 'ssr'), module resolution uses ssr.resolve.conditions. This applies to both package exports and subpath imports.
You can learn more about Vite environments and Vitest environments in environment.
:::
Running native NodeJS modules in pool: 'threads' can run into cryptic errors coming from the native code.
Segmentation fault (core dumped)thread '<unnamed>' panicked at 'assertion failedAbort trap: 6internal error: entered unreachable codeIn these cases the native module is likely not built to be multi-thread safe. As a workaround, you can switch to pool: 'forks' which runs the test cases in multiple node:child_process instead of multiple node:worker_threads.
::: code-group
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
pool: 'forks',
},
})
vitest --pool=forks
:::
Setting process.env.TZ in a setup file or in a test, or setting TZ via env, has no effect on Date in pool: 'threads' and pool: 'vmThreads'. Node.js applies TZ only when the main thread sets it. A worker thread sees the new value on process.env, but keeps the time zone of the main process.
process.env.TZ = 'Asia/Tokyo'
new Date('2026-01-01T00:00:00Z').getHours() // 9 in forks, unchanged in threads
Set the time zone before workers start. Use the shell, the config file, or globalSetup; all of them run in the main process and work in every pool.
::: code-group
TZ=Asia/Tokyo vitest
import { defineConfig } from 'vitest/config'
process.env.TZ = 'Asia/Tokyo'
export default defineConfig({})
export default function () {
process.env.TZ = 'Asia/Tokyo'
}
:::
If tests need different time zones at runtime, use pool: 'forks' or pool: 'vmForks', where each worker is a separate process, or pass the timeZone option to Intl.DateTimeFormat instead of changing TZ.
This error happens when a Promise rejects but no .catch() handler or await is attached to it before the microtask queue flushes. This behavior comes from JavaScript itself and is not specific to Vitest. Learn more in the Node.js documentation.
A common cause is calling an async function without awaiting it:
async function fetchUser(id) {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) {
throw new Error(`User ${id} not found`) // [!code highlight]
}
return res.json()
}
test('fetches user', async () => {
fetchUser(123) // [!code error]
})
Because fetchUser() is not awaited, its rejection has no handler and Vitest reports:
Unhandled Rejection: Error: User 123 not found
await the promise so Vitest can catch the error:
test('fetches user', async () => {
await fetchUser(123) // [!code ++]
})
If you expect the call to throw, use expect().rejects:
test('rejects for missing user', async () => {
await expect(fetchUser(123)).rejects.toThrow('User 123 not found')
})
Some packages work in an app build but fail in Vitest because they are only valid after a bundler has rewritten or resolved them. When Vitest externalizes a dependency, Node.js loads it directly, so Node's ESM and package rules apply. See Node.js documentation on ECMAScript modules and packages for the precise rules.
Common examples include packages that:
.js files without "type": "module"exports, imports, main, or module entriesYou might see errors such as:
Cannot find module './relative-path' imported from ...Unexpected token 'export'Cannot use import statement outside a moduleModule ... seems to be an ES Module but shipped in a CommonJS package.Unknown file extension ".css"When possible, fix the package so Node.js can load it directly: add "type": "module" for ESM .js files, use .mjs, include explicit file extensions in ESM imports, and make sure exports points to files Node.js can load.
If you cannot fix the package itself, inline it so Vite handles it instead of passing it to Node.js as an external dependency. Inline the whole dependency chain that leads to the invalid package. If your source imports wrapper-package, and wrapper-package imports broken-package, inline both packages:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
server: {
deps: {
inline: ['wrapper-package', 'broken-package'],
},
},
},
})
You can also use Vite's ssr.resolve.noExternal for the same purpose. Vitest merges ssr.resolve.noExternal into server.deps.inline, so this is useful when the dependency also needs to be bundled by Vite in SSR builds:
import { defineConfig } from 'vitest/config'
export default defineConfig({
ssr: {
resolve: {
noExternal: ['wrapper-package', 'broken-package'],
},
},
})
Vitest is ESM-first. By default, source files run in Vite's module runner, which provides CommonJS variables such as require, module, and exports for compatibility but does not reproduce Node.js CommonJS semantics completely.
Calls to require() always use Node.js directly and leave the module runner. As a result:
instanceof checksIf your project uses CommonJS and doesn't need Vite transforms, set experimental.viteModuleRunner to false so the whole module graph is loaded by the native runtime:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
experimental: {
viteModuleRunner: false,
},
},
})
If the application uses ESM source but imports a CommonJS package from the same monorepo, you can instead use server.deps.external to externalize the complete CommonJS package. This keeps its entry points and internal require() calls in the same native module cache. For example:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
server: {
deps: {
external: [/\/packages\/legacy-cjs\//],
},
},
},
})