Solving Environment Clash with Vitest Workspaces
Stop fighting with environment comments. Learn how to use Vitest Workspaces to run Node and JSDOM tests concurrently in a single project without configuration leakage.
08 Jul 2026, 08:36 UTC

The Problem: The 'One-Size-Fits-All' Test Config
In many modern TypeScript projects, you eventually hit a wall where your tests need two different worlds. You might have a set of utility functions that must run in a pure node environment for speed and filesystem access, while your UI components require jsdom or happy-dom to simulate a browser.
The traditional approach is to either use a single environment (forcing the browser emulator on tests that don't need it, which slows down execution) or maintain separate test scripts and configuration files that you trigger manually. This fragmentation makes CI pipelines brittle and developer experience sluggish.
The takeaway: Vitest Workspaces allow you to define multiple project configurations in one place, letting you run node-based and browser-based tests concurrently under a single runner process without configuration leakage.
How Workspaces Isolate Environments
A Vitest Workspace is essentially a mapping system. Instead of one vitest.config.ts ruling the entire repository, you create a vitest.workspace.ts file at the root. This file tells Vitest: "If a test file matches this pattern, apply this specific configuration."
This is particularly powerful for monorepos. You can have a /packages/api folder using a Node environment and a /packages/ui folder using JSDOM. Vitest optimizes this by sharing the same runner process, but it maintains isolated configuration contexts. This means your global mocks for the API won't accidentally pollute the environment for your UI components.
Implementation: A Dual-Environment Setup
To implement this, you need a workspace configuration file and individual config files for your projects. This example assumes you are using Vitest v1.0+ and have jsdom installed as a dev dependency.
1. The Root Workspace Definition
Create vitest.workspace.ts in your project root. This file defines the projects Vitest should orchestrate.
import { defineWorkspace } from 'vitest/config'export default defineWorkspace([ './vitest.config.node.ts', './vitest.config.dom.ts' ])
2. The Node Configuration
Create vitest.config.node.ts for your backend or utility tests.
import { defineConfig } from 'vitest/config'export default defineConfig({ test: { name: 'node-tests', environment: 'node', include: ['src/utils/**/*.test.ts'] } })
3. The DOM Configuration
Create vitest.config.dom.ts for your frontend tests.
import { defineConfig } from 'vitest/config'export default defineConfig({ test: { name: 'dom-tests', environment: 'jsdom', include: ['src/components/**/*.test.ts'], setupFiles: ['./src/setup-dom.ts'] } })
Running and Verifying
Run the tests from the root directory using your standard test command (e.g., npm test or npx vitest). You do not need to specify a config file; Vitest automatically detects the vitest.workspace.ts file.
Verification: Look at the CLI output. Vitest will label the tests by the name property defined in the configs (e.g., node-tests and dom-tests). To verify the environment is actually switching, you can add a temporary check in a test file:
// In a dom-test file
expect(window).toBeDefined() // Should pass in dom-tests, fail in node-tests
Trade-offs and Limitations
While workspaces solve the environment clash, they introduce a few engineering trade-offs:
- Memory Overhead: Initializing multiple environments (especially JSDOM) simultaneously can lead to higher memory consumption. In very large monorepos, you may need to limit concurrency via the
--max-threadsor--max-forksflags. - Setup Duplication: If you have shared setup logic (like custom matchers), you may find yourself importing them into multiple
setupFilesacross different workspace configs. - Configuration Drift: Because settings are split across files, it is easy for one project's config to fall out of sync with another (e.g., different TypeScript alias settings).
Actionable Closing
If your project currently uses // @vitest-environment jsdom comments at the top of every single UI test file, you are managing configuration at the file level, which is tedious and error-prone. Move those definitions into a vitest.workspace.ts file. By shifting the environment logic to the configuration layer, you gain a cleaner codebase and a more predictable test execution pipeline.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.