Vitest

Vitest is a test runner built on top of Vite. It targets the same job as Jest -- running unit and component tests in a simulated environment -- but is built around native ES Modules and Vite's transform pipeline instead of Jest's own transformer and module system. Next.js officially supports Vitest as an alternative to Jest, with a maintained with-vitest example in the Next.js repository that scaffolds a working setup.

Why Teams Choose Vitest

Jest remains the more established choice with the larger ecosystem of plugins and Stack Overflow answers; Vitest is generally the pick when raw test speed and ESM-native tooling matter more than ecosystem maturity. Either is a fully supported, valid choice for a Next.js project -- see Jest for the alternative setup.

Install Vitest and the same React Testing Library packages used with Jest -- the rendering and querying APIs from React Testing Library don't change between runners.

npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/jest-dom


Configuration

Where Jest uses the next/jest helper to bridge Next.js's SWC transform into Jest's config, Vitest instead uses the standard Vite React plugin directly -- there's no Next.js-specific wrapper needed, since Vitest already shares Vite's transform pipeline. The main config differences from a jest.config.js are the plugins array (Vite's plugin system instead of Jest transforms) and the test block living inside the same Vite config shape.

A vitest.config.ts roughly equivalent to the jest.config.js shown on the Jest page.

vitest.config.ts:
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    setupFiles: ['./vitest.setup.ts'],
    globals: true,
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './'),
    },
  },
})

vitest.setup.ts:
import '@testing-library/jest-dom'

Add a test script -- the CLI itself is deliberately Jest-like:


package.json:
{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest"
  }
}


A Component Test, Ported from Jest

This is the same Counter component from the Jest page, tested with Vitest instead. Notice the test body itself is nearly identical -- render, screen, and userEvent all come from the same @testing-library/* packages regardless of which runner executes them. The only Vitest-specific line is the vi import used for spies/mocks, which plays the same role as Jest's global jest object.

With globals: true set in vitest.config.ts, describe/it/expect don't need to be imported -- shown explicitly here for clarity.

components,Counter.test.tsx:
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Counter from './Counter'

describe('Counter', () => {
  it('starts at zero and increments on click', async () => {
    const user = userEvent.setup()
    render(<Counter />)

    expect(screen.getByText('Count: 0')).toBeInTheDocument()

    await user.click(screen.getByRole('button', { name: /increment/i }))

    expect(screen.getByText('Count: 1')).toBeInTheDocument()
  })
})


Same Limitations as Jest

Vitest runs in Node.js against jsdom, just like Jest -- swapping the runner doesn't change what layer of the app it can reach. Async Server Components, Server Actions exercised through a real request, and anything else that depends on a genuinely running Next.js server are still outside what Vitest and React Testing Library can test directly. Use Playwright for that layer regardless of which unit test runner you choose.