Vitest Setup with Next.js
Configure Vitest as a fast, modern test runner for your Next.js App Router project.
Search across all documentation pages
Configure Vitest as a fast, modern test runner for your Next.js App Router project.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Quick-reference recipe card -- copy-paste ready.
# Install dependencies
npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event// 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",
globals: true,
setupFiles: ["./vitest.setup.ts"],
include: ["**/*.test.{ts,tsx}"],
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});// vitest.setup.ts
import "@testing-library/jest-dom/vitest";// package.json scripts
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}When to reach for this: When starting a new Next.js project and you want a fast, Vite-native test runner with near-instant HMR-based watch mode.
// src/components/greeting.tsx
interface GreetingProps {
name: string;
}
export function Greeting({ name }: GreetingProps) {
return <h1>Hello, {name}!</h1>;
}// src/components/greeting.test.tsx
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { Greeting } from "./greeting";
describe("Greeting", () => {
it("renders the name", () => {
render(<Greeting name="Alice" />);
expect(screen.getByRole("heading")).toHaveTextContent("Hello, Alice!");
});
it("updates when name prop changes", () => {
const { rerender } = render(<Greeting name="Alice" />);
expect(screen.getByRole("heading")).toHaveTextContent("Hello, Alice!");
rerender(<Greeting name="Bob" />);
expect(screen.getByRole("heading")).toHaveTextContent("Hello, Bob!");
});
});What this demonstrates:
@/ from tsconfig@vitejs/plugin-react without needing a separate Babel configenvironment: "jsdom" setting creates a simulated browser DOM in Node.js for each test fileglobals: true makes describe, it, expect available without importing them (matching Jest conventions)@testing-library/jest-dom/vitest adds matchers like toBeInTheDocument() and toHaveTextContent()Using happy-dom instead of jsdom:
// vitest.config.ts
export default defineConfig({
test: {
environment: "happy-dom", // faster but less complete DOM implementation
},
});Per-file environment override:
// @vitest-environment happy-dom
import { describe, it } from "vitest";
// This file uses happy-dom regardless of global configCoverage with v8 provider:
npm install -D @vitest/coverage-v8// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["text", "html", "lcov"],
include: ["src/**/*.{ts,tsx}"],
exclude: ["src/**/*.test.{ts,tsx}", "src/**/*.d.ts"],
},
},
});App Router path aliases from tsconfig:
// vitest.config.ts - match tsconfig paths exactly
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
environment: "jsdom",
globals: true,
setupFiles: ["./vitest.setup.ts"],
},
});npm install -D vite-tsconfig-paths// If using globals: true, add vitest types to tsconfig
// tsconfig.json
{
"compilerOptions": {
"types": ["vitest/globals", "@testing-library/jest-dom"]
}
}Missing @vitejs/plugin-react -- Without this plugin, JSX in test files fails to transform. Vitest does not automatically handle JSX like Jest with Babel does. Fix: Always include react() in the plugins array.
Path aliases not resolving -- If you use @/components/... imports, Vitest does not read tsconfig.json paths by default. Fix: Either set alias manually in vitest.config.ts or use vite-tsconfig-paths.
globals: true but TypeScript errors -- TypeScript does not know about Vitest globals unless you add "vitest/globals" to your compilerOptions.types. Fix: Update tsconfig.json as shown above.
jsdom vs happy-dom -- happy-dom is faster but lacks some DOM APIs (getBoundingClientRect, IntersectionObserver). If tests fail with happy-dom, switch to jsdom.
Next.js server-only code -- Vitest cannot run code that uses next/headers, next/cache, or other Node-only Next.js APIs in a jsdom environment. Fix: Mock those modules or test them separately.
| Alternative | Use When | Don't Use When |
|---|---|---|
Jest with next/jest | You have an existing Jest setup or need the broader Jest ecosystem | You want faster watch mode and native ESM support |
| Playwright Component Testing | You need real browser rendering for component tests | You want fast unit tests that run in Node |
| Bun test runner | Your project uses Bun as its runtime | You need the Vitest/Jest ecosystem of matchers and plugins |
react() plugin, any JSX in test or source files will fail to compile.react() in the plugins array of vitest.config.ts.jsdom is a more complete browser DOM implementation but slower.happy-dom is faster but lacks some APIs like getBoundingClientRect and IntersectionObserver.happy-dom, switch to jsdom.Set globals: true in vitest.config.ts and add "vitest/globals" to your tsconfig.json compilerOptions.types array.
@testing-library/jest-dom/vitest adds matchers like toBeInTheDocument() and toHaveTextContent().Either set alias manually in vitest.config.ts:
alias: {
"@": path.resolve(__dirname, "./src"),
}Or install and use vite-tsconfig-paths as a plugin.
globals: true makes them available at runtime, but TypeScript does not know about them."vitest/globals" to compilerOptions.types in tsconfig.json.npm install -D @vitest/coverage-v8Then add coverage.provider: "v8" and your desired reporters in vitest.config.ts.
Yes. Add a comment at the top of the test file:
// @vitest-environment happy-domThis overrides the global environment setting for that file only.
jsdom environment.Import defineConfig from vitest/config:
import { defineConfig } from "vitest/config";
export default defineConfig({ /* ... */ });This gives you full type-checking and autocompletion for all Vitest config options.
vitest starts watch mode, re-running tests when files change.vitest run executes all tests once and exits -- use this in CI.It uses Vite's module graph to track dependencies. Only tests affected by changed files are re-run, making it much faster than Jest's watch mode.
Reviewed by Chris St. John·Last updated Jul 10, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥