Solving Environment Conflict with Vitest Workspaces
Stop juggling multiple Vitest config files. Learn how to use Vitest Workspaces to manage Node and JSDOM environments in a single, unified test run.
31 May 2026, 11:03 UTC

The Environment Tug-of-War
In many modern TypeScript projects, you often find yourself needing two completely different execution contexts. Your backend utility functions need a lean node environment for speed and native API access, while your frontend components require jsdom or happy-dom to simulate a browser.
The traditional solution is to create multiple configuration files (e.g., vitest.config.node.ts and vitest.config.dom.ts) and run them as separate CLI commands. This fragments your CI pipeline, doubles your startup overhead, and makes it difficult to get a single, unified report of your project's health. The problem isn't the tests themselves, but the configuration overhead of managing disparate environments.
Consolidating with Vitest Workspaces
Vitest Workspaces allow you to define multiple project configurations within a single vitest.workspace.ts (or .js) file. Instead of treating your test suites as separate entities, Workspaces treat them as distinct projects sharing a single runner.
This approach allows you to map specific directories or file patterns to specific environments. When you run vitest, the runner analyzes the workspace configuration, assigns the correct environment to each test file based on your glob patterns, and executes them concurrently.
Implementing a Multi-Environment Setup
To implement this, you move your environment-specific logic out of a global config and into a workspace definition. Assume a project structure where /src/api contains Node logic and /src/components contains UI logic.
Create a vitest.workspace.ts file in your root directory:
import { defineWorkspace } from 'vitest/config';
export default defineWorkspace([
{
// Project 1: Node.js utilities
test: {
name: 'node-tests',
include: ['src/api/**/*.test.ts'],
environment: 'node',
},
},
{
// Project 2: Browser-based components
test: {
name: 'dom-tests',
include: ['src/components/**/*.test.ts'],
environment: 'jsdom',
setupFiles: ['./src/components/test-setup.ts'],
},
},
]);
Execution and Verification
Run the tests from your terminal using the standard command:
# Run from project root with standard permissions
npm test
# or
npx vitest
Verification: To ensure the environments are correctly assigned, you can add a temporary check in your tests. In a node-tests file, try accessing window; it should be undefined. In a dom-tests file, window should be available. If a Node test accidentally runs in JSDOM, you will see a significant increase in execution time and potential memory leaks.
Trade-offs and Resource Constraints
While Workspaces simplify the developer experience, they introduce a memory trade-off. Each environment (especially JSDOM) carries its own overhead. Running a massive suite of DOM tests alongside Node tests in a single process can lead to higher memory consumption than running them in separate, sequential processes.
Additionally, glob pattern overlap is a common risk. If a file matches patterns in two different workspace projects, Vitest may attempt to run the test twice in different environments, leading to confusing duplicate failures or successes.
Practical Decision Matrix
| Scenario | Recommended Approach | Reasoning |
|---|---|---|
| Pure Node or Pure DOM | Single vitest.config.ts |
No need for workspace complexity. |
| Mixed Environments (Monorepo) | vitest.workspace.ts |
Unified reporting and concurrent execution. |
| Extremely Large Test Suites | Separate Configs/CI Jobs | Avoids memory exhaustion on limited CI runners. |
Closing Action
If you are currently maintaining multiple vitest.config files or manually passing environment flags via the CLI, migrate to a vitest.workspace.ts file. Start by defining two clear, non-overlapping glob patterns for your Node and DOM tests to streamline your local development loop.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.