happy-dom vs jsdom (2026): Which Is Faster for Vitest?
TL;DR
happy-dom is 2-4x faster than jsdom and works great for most React/Vue component tests with Vitest. jsdom (~27M weekly downloads) has been the standard DOM simulation environment for 10+ years — more complete, more battle-tested. happy-dom (~3M downloads) sacrifices edge-case completeness for speed. For Vitest users with large test suites, happy-dom is worth the switch. For Jest or tests that rely on obscure DOM APIs, stick with jsdom.
Key Takeaways
- jsdom: ~27M weekly downloads — happy-dom: ~3M (npm, March 2026)
- happy-dom is 2-4x faster — significant for large test suites
- jsdom has better DOM compatibility — more complete browser API support
- happy-dom is Vitest's recommended default — Vitest docs suggest happy-dom
- Both simulate a browser environment — neither runs a real browser (use Playwright for that)
What These Libraries Do
Both jsdom and happy-dom implement the browser DOM and Web APIs in Node.js, allowing component tests to run without a real browser:
Test runner (Node.js)
↓
DOM simulation (jsdom OR happy-dom)
↓
Your React/Vue component
↓
Rendered virtual DOM
↓
Assertions via Testing Library
Neither is a real browser — they are JavaScript approximations of browser APIs. For true cross-browser testing or tests that depend on real CSS rendering, layout, and paint behavior, use Playwright or Cypress against a real browser. jsdom and happy-dom exist to make component unit tests fast and runnable in Node.
Performance Benchmark
The speed difference between happy-dom and jsdom is substantial enough to matter on any real project. The gap comes from happy-dom's design philosophy: implement browser APIs with a focus on the common path, sacrificing edge-case spec compliance for speed. jsdom aims for much higher spec compliance, which requires more complex bookkeeping on every DOM operation.
For a test suite with 500 React component tests using React Testing Library:
Test suite: 500 React component tests (React Testing Library)
Environment Time vs happy-dom
----------- -------- -------------
happy-dom ~18s baseline
jsdom ~45s ~2.5x slower
Test suite: 100 simple component tests
Environment Time vs happy-dom
----------- -------- -------------
happy-dom ~4s baseline
jsdom ~10s ~2.5x slower
The speedup compounds with test suite size. A team running 2,000 component tests sees the biggest gains — the difference between a 3-minute CI run and a 7-minute CI run is meaningful for developer iteration speed.
The speedup comes from two sources: happy-dom initializes its DOM environment faster per test file (important when you have many test files), and it processes DOM mutations more quickly during test execution. jsdom's thorough event propagation model and comprehensive CSS support add overhead even when tests don't use those features.
Configuration
Switching environments in Vitest is a one-line change globally, or a per-file comment for surgical control:
// vitest.config.ts — global environment setting
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'happy-dom', // Switch here: 'happy-dom' | 'jsdom' | 'node'
setupFiles: ['./src/test/setup.ts'],
},
});
// src/test/setup.ts — works with both environments
import '@testing-library/jest-dom';
// Adds toBeInTheDocument, toHaveValue, toBeVisible, etc.
For per-file overrides, add a comment at the very top of the test file:
// src/components/chart.test.tsx
// @vitest-environment jsdom
// This file uses complex CSS and needs jsdom's more complete implementation
import { render, screen } from '@testing-library/react';
import { ChartComponent } from './ChartComponent';
test('renders chart with correct labels', () => {
render(<ChartComponent data={[1, 2, 3]} />);
expect(screen.getByRole('img', { name: /chart/i })).toBeInTheDocument();
});
For Jest users, happy-dom requires a separate package:
// jest.config.js
module.exports = {
// Default: 'node'
// Standard: testEnvironment: 'jsdom'
// happy-dom for Jest:
testEnvironment: 'jest-environment-happydom',
};
Vitest ships with both happy-dom and jsdom built in — no additional installation needed for either. Jest requires explicit setup for either non-default environment.
API Compatibility Gaps
Where happy-dom diverges from real browser behavior based on known issues and community reports:
CSS getComputedStyle() — happy-dom's computed style implementation is incomplete. It handles inline styles and basic CSS properties well, but cascaded styles from stylesheets, CSS custom properties via getPropertyValue(), and computed values for layout properties (like display: flex effects on children) may return incorrect or empty values.
window.location behaviors — Some navigation patterns, particularly involving history.pushState and the interaction between URL updates and the location object, have known edge cases in happy-dom that behave differently from real browsers or jsdom.
IntersectionObserver — Both happy-dom and jsdom implement stub versions of IntersectionObserver that do not actually perform intersection calculations. If your component logic depends on real intersection detection, you'll need to mock it regardless of which environment you use.
Shadow DOM — happy-dom's Shadow DOM support is improving but lags behind jsdom's more complete implementation. Components that rely heavily on custom elements and Shadow DOM encapsulation may see failures with happy-dom.
Canvas API — Both environments have limited Canvas support. canvas.getContext('2d') returns an object, but drawing methods and pixel data APIs are stubs. For Canvas-heavy component tests, neither environment is a substitute for a real browser.
// APIs both support well — the common path for React/Vue tests
document.createElement('div');
document.querySelector('.class');
element.addEventListener('click', handler);
window.localStorage.setItem('key', 'value');
fetch('https://api.example.com'); // via msw mocking
window.dispatchEvent(new CustomEvent('resize'));
new MutationObserver(callback).observe(target, config);
new ResizeObserver(callback).observe(element);
// APIs where jsdom has better support
// - Complex CSS cascade and computed styles
// - SVG manipulation (partial in happy-dom)
// - CSS custom properties via getComputedStyle
// - Some window.location edge cases
// - Shadow DOM (improving in happy-dom)
In practice, most React and Vue component tests that use React Testing Library or Vue Testing Library do not hit these gaps. The Testing Library philosophy of testing from the user's perspective — clicking, typing, reading accessible labels — maps well to what both environments support.
Migration Checklist
Switching from jsdom to happy-dom takes 15 minutes for most projects. Here's the process:
Step 1: Update vitest.config.ts
// Before
environment: 'jsdom'
// After
environment: 'happy-dom'
Step 2: Run your test suite
pnpm vitest run
Step 3: Triage failures
Most failures fall into two categories:
- Real failures you hadn't caught — bugs in your components that jsdom was silently ignoring. Fix these normally.
- happy-dom API gaps — tests that fail because happy-dom doesn't support a specific API your test uses.
For the second category, the fix is usually a per-file environment override:
// Add to the top of the affected test file
// @vitest-environment jsdom
Step 4: Add targeted mocks for common APIs
Some mocks are needed in both environments but are often added during migration:
// src/test/setup.ts — common browser API mocks
import '@testing-library/jest-dom';
// matchMedia — not implemented in either environment
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
// scrollTo — stubbed in both environments
window.scrollTo = vi.fn();
Step 5: Accept the hybrid approach
The goal is not 100% happy-dom. The pragmatic outcome is: most tests run on happy-dom (fast), a small set of tests that need deeper DOM compatibility run on jsdom (via per-file comment). This hybrid approach is explicitly supported by Vitest and is the recommended pattern in the docs.
A typical outcome after migration: 95% of test files on happy-dom, 5% still on jsdom — with an overall suite time reduction of 40-60%.
Package Health
| Package | Weekly Downloads | Maintainers |
|---|---|---|
jsdom | ~27M | Domenic Denicola + community |
happy-dom | ~3M | David Konsumer + contributors |
jest-environment-jsdom | ~18M | Jest team (bundles jsdom) |
jest-environment-happydom | ~400K | Community |
jsdom (~27M weekly downloads) includes all Jest usage via jest-environment-jsdom. Its download count dwarfs happy-dom because Jest's default environment recommendation was jsdom for years. The project is actively maintained with Domenic Denicola (WHATWG spec editor) contributing, which gives it unusually strong browser spec alignment.
happy-dom (~3M downloads) is growing steadily as the Vitest ecosystem matures. David Konsumer and the contributors are active and responsive on GitHub. The project prioritizes speed and pragmatic completeness — the issue tracker is addressed quickly, and API gaps are regularly closed. The 3M number does not include transitive Jest usage, so the active user count is meaningfully different from jsdom's.
When to Choose
Choose happy-dom when:
- You're using Vitest and want the recommended default environment
- Your test suite has 200+ component tests where 2-4x speed improvement matters
- Your tests are standard React, Vue, or Svelte component tests using Testing Library
- You want faster CI feedback loops without sacrificing meaningful coverage
- You're willing to add per-file jsdom fallbacks for the small percentage of edge-case tests
Choose jsdom when:
- You're using Jest, where jsdom is the default with the most documentation and support
- Your tests depend on complex CSS cascade, computed styles, or CSS custom properties
- Your components use Shadow DOM, SVG manipulation, or Canvas API
- You want maximum browser API compatibility and don't want to manage per-file environment overrides
- Your team is unfamiliar with happy-dom and migration risk outweighs the speed benefit
- Compare happy-dom and jsdom package health on PkgPulse.
- Read our guide on how to migrate from Enzyme in 2026 for React Testing Library patterns that work well with both environments.
- Explore the Vitest package page for version trends and ecosystem adoption data.
See the live comparison
View happy dom vs. jsdom on PkgPulse →