How to Speed Up Vitest
Vitest running slow? 12 practical ways to speed up Vitest locally and in CI: pool tuning, isolate false, changed-only runs, sharding, and cache fixes.
BuildPulse Team
May 20, 2026

If you're shipping code multiple times a day, waiting on sluggish test suites feels like death by a thousand cuts. Vitest is already one of the fastest test frameworks around, but "fast by default" still leaves a lot of speed on the table, especially in CI.
Here's a practical, current guide to speeding up Vitest in 2026: what to flip on, what to turn off, and which flags actually exist (a few older guides, including a previous version of this one, recommended flags that Vitest has since replaced).
Looking to profile and understand why Vitest is slow rather than a checklist of fixes? Read our companion deep-dive, Vitest performance explained, then come back here for the fixes.
Why Fast Tests Are a Big Deal
- Faster iteration: feedback in seconds keeps you in flow.
- Fearless refactoring: quick tests get run more often, catching issues early.
- Lower CI costs: less compute per pipeline run adds up fast at scale.
- Happier devs: nobody enjoys staring at a spinning CI wheel.
1. Run Once in CI: vitest run
Watch mode is great locally and pure waste in CI. Use the run command so Vitest executes once and exits:
vitest run
Add --silent to cut log noise and make CI output easier to scan:
vitest run --silent

A clean single run: 18 tests across 8 files in 1.23s, with the time split across transform, import, and test phases.
--silent suppresses console output from your code under test. If you need that output for debugging a failure, drop the flag for the rerun rather than leaving logs on for every run.
2. Pick the Right Pool and Worker Count
Older guides (and an earlier version of this post) told you to tune --threads. That flag is gone. Vitest now runs tests through a pool: forks (child processes, the default), threads (worker threads), or vmThreads.
Two levers matter:
# cap concurrency to match your CI runner's CPUs
vitest run --maxWorkers 4
# threads are often faster than forks if your code tolerates them
vitest run --pool threads
Or in vitest.config.ts:
export default defineConfig({
test: {
pool: 'threads',
poolOptions: {
threads: { maxThreads: 4, minThreads: 2 },
},
},
})
threads is usually faster than forks but breaks on code that relies on process-level globals or native modules that aren't thread-safe. Try it; if weird failures appear, go back to forks.
On CI runners, match maxWorkers to the actual core count. Oversubscribing a 2-core runner with 8 workers is slower than 2 workers, not faster.
3. The Biggest Single Lever: isolate false
By default Vitest gives every test file a fresh, isolated environment. That safety costs real time. If your tests don't leak state between files (most well-written unit suites don't), turning isolation off is routinely a 2-5x speedup:
export default defineConfig({
test: {
isolate: false,
},
})
The tradeoff is that state leaking between test files can cause order-dependent flakiness. We wrote up how to adopt this safely, including how to find the tests that break, in Vitest isolate: false without the flaky fallout.
For single-file debugging you can also skip isolation ad hoc:
vitest run --no-isolate path/to/file.test.ts
4. Run Only What Changed: --changed
Running the whole suite for every local edit is overkill. Test only files affected by your latest changes:
# uncommitted changes
vitest --changed
# everything since a ref (great for PR builds)
vitest run --changed origin/main
Vitest walks the module graph, so a change to a shared util still runs the tests that import it.

After touching one source file, --changed runs just the 2 affected test files in 268ms instead of the full suite's 1.23s.
5. Keep Sequential Tests Honest
Some tests genuinely can't run concurrently (shared files, a test database). Vitest's real API for that is sequential, not test.serial() (which belongs to a different framework and silently doesn't exist here):
import { describe, test } from 'vitest'
describe.sequential('writes to the shared fixture file', () => {
test('step one', async () => { /* ... */ })
test('step two', async () => { /* ... */ })
})
Better still: give each test its own temp file or DB schema and let them parallelize.
6. Shard Across CI Machines (Built In)
You don't need a third-party splitter. Vitest shards natively:
# machine 1 of 3
vitest run --shard=1/3
GitHub Actions matrix example:
strategy:
matrix:
shard: [1, 2, 3]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx vitest run --shard=${{ matrix.shard }}/3 --reporter=junit --outputFile=results-${{ matrix.shard }}.xml
Three 2-minute shards beat one 6-minute job, and a flaky shard reruns in a third of the time.

Shard 1 of 3 picks up 3 of the suite's 8 files; each CI job runs its slice and finishes in a fraction of the full-suite time.
7. Use the Cache, and Know When to Clear It
Vitest caches transformed modules so repeat runs skip work. In CI, persist the cache directory (by default under node_modules/.vite) between runs along with your dependency cache.
If you're seeing stale-transform weirdness after a big dependency bump:
vitest run --clearCache
We cover what the cache actually stores, and what to persist in CI, in Vitest cache, explained.
8. Streamline Your Vite Config for Tests
Vitest inherits your Vite pipeline, so test startup pays for every plugin you load. Strip plugins that don't matter for tests:
export default defineConfig(({ mode }) => ({
plugins: mode === 'test' ? [] : [vue(), someHeavyPlugin()],
}))
Image loaders, PWA plugins, legacy-browser transforms: none of them belong in a test run.
9. Mock Slow Dependencies
Network calls, file I/O, and real databases drag unit tests down and add flakiness. Mock them:
vi.mock('axios', () => ({
default: { get: vi.fn(() => Promise.resolve({ data: {} })) },
}))
Save real-dependency coverage for a small, explicitly integration-tagged suite.
10. Let esbuild Skip Type Checking
Vitest transpiles TypeScript with esbuild and does not type-check. Keep it that way: run tsc --noEmit as a separate CI step (or rely on your editor) instead of bolting type-checking plugins into the test pipeline.
// tsconfig.json
{ "compilerOptions": { "isolatedModules": true } }
11. Hunt Down Slow Tests
Find the offenders before optimizing blind:
vitest run --reporter=verbose

Per-test timings make the outlier obvious: every unit test finishes in single-digit milliseconds while one integration test eats 404ms.
Tests slower than the slowTestThreshold (default 300ms) get flagged in output. Tighten it to surface more candidates:
export default defineConfig({
test: { slowTestThreshold: 150 },
})
Then fix the usual suspects: real timers (use vi.useFakeTimers()), real network calls, and oversized fixture setup.
12. Fail Fast When You Want Fast Feedback
On PR builds where any failure means "go fix it," stop early:
vitest run --bail=1
Keep full runs (no bail) for main-branch builds where you want the complete failure picture.
Bonus: CI/CD Checklist
- Cache aggressively: node_modules (via lockfile hash) plus the Vite/Vitest cache directory.
- Shard with
--shardacross a matrix (see section 6). - Right-size workers to the runner's real core count.
- Skip coverage on every push if you don't act on it; run coverage nightly or on main only. V8 coverage (
--coverage.provider=v8) is cheaper than istanbul.
Final Thoughts
Take an afternoon: profile with the verbose reporter, flip isolate: false if your suite tolerates it, tune the pool to your CI hardware, and shard. Those four moves alone usually cut Vitest CI time by more than half.
All of these changes speed up your tests, but at some point the hardware and execution environment become the bottleneck. BuildPulse Runners run your GitHub Actions jobs 2x faster at half the cost, with zero tooling changes.
Stop guessing which tests you can trust
BuildPulse finds your flaky tests, ranks them by the engineering time they cost, and lets you quarantine the worst in one click. See results on your first build.
Free to start · No credit card required · Setup is a single CI step
Related posts