How to Speed Up Jest in CI
Jest is fast locally but stumbles in CI when worker counts and caching are wrong. A few flags recover most of the gap.
Jest parallelizes across worker processes and caches transformed modules. In CI the defaults often misjudge the host, so setting workers, sharding, and persisting the cache matter.
1. Set workers explicitly
CI runners report misleading CPU counts. Pin --maxWorkers to the real core count to avoid oversubscription thrash.
- run: npx jest --maxWorkers=4 --ci2. Shard across machines
Jest supports native sharding to split the suite across runners.
strategy:
matrix:
shard: [1/3, 2/3, 3/3]
steps:
- run: npx jest --shard=${{ matrix.shard }} --ci3. Persist the Jest cache
Cache the transform cache directory so transpilation is reused across runs.
- uses: actions/cache@v4
with:
path: /tmp/jest-cache
key: jest-${{ hashFiles('package-lock.json') }}
- run: npx jest --cacheDirectory=/tmp/jest-cache --ci4. Run only affected tests on PRs
Use jest --changedSince=origin/main on PR runs so the suite scales with the diff, not the repo. Keep the full run on main.
Measure before you optimise
Pipeline optimisation usually targets the step people assume is slow. Get the real per-step timings first, because the answer is frequently dependency install or a cold cache rather than the build itself.
# per-job timings for the last 20 runs
gh run list --limit 20 --json databaseId,conclusion,createdAt,updatedAt \
--jq '.[] | "\(.conclusion)\t\(.createdAt)\t\(.updatedAt)"'
# per-step timing inside one run
gh run view <run-id> --log | grep -E "^\S+\s+.*Run |##\[group\]" | head -40Key takeaways
- Pin --maxWorkers to the real core count to avoid oversubscription.
- Use --shard to spread tests across runners.
- Persist the Jest transform cache and run --changedSince on PRs.