Type something to search...
Next.js Setting up Vitest

Next.js Setting up Vitest

Vitest is the newer entrant among this series' four testing tools, built from the ground up around Vite's tooling rather than retrofitted onto an older bundler-agnostic design the way Jest originally was — and the practical result most teams notice first is meaningfully faster test execution, particularly as a test suite grows large. This article covers setting it up for unit testing in a Next.js App Router project, and the one limitation it shares with Jest that's worth knowing up front.

The same limitation as Jest, stated the same way

Vitest currently doesn't support unit-testing async Server Components, for the identical reason covered elsewhere in this series: the pattern is new enough to the broader React ecosystem that testing tooling hasn't fully caught up to it yet across the board — this isn't a Vitest-specific shortfall relative to its competitors. Synchronous Server and Client Components unit-test fine; for the async case specifically, reach for End-to-End testing via Playwright or Cypress instead.

The fastest path

npx create-next-app@latest --example with-vitest with-vitest-app

Setting it up manually

# TypeScript
npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom vite-tsconfig-paths

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

The one package that differs between the two setups is vite-tsconfig-paths — worth understanding why it's TypeScript-specific rather than treating it as an arbitrary inclusion: it teaches Vitest to resolve module path aliases directly from your existing tsconfig.json, which is naturally only relevant when there's a tsconfig.json present to read from in the first place. A JavaScript project has no equivalent file for it to consult, so the package simply has nothing to do there.

// vitest.config.mts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  plugins: [tsconfigPaths(), react()],
  test: {
    environment: "jsdom",
  },
});

environment: 'jsdom' deserves a beat of explanation, because it's easy to treat as boilerplate rather than understand: Vitest's actual default test environment is node, which has no DOM APIs at all — no document, no window. Since you're testing React components that render into a DOM, you need jsdom (a JavaScript-based DOM implementation) standing in for a real browser. Omitting this setting doesn't produce a helpful error pointing at the missing environment; it tends to surface instead as confusing failures the moment your test touches anything DOM-related, since the code is executing against an environment that simply has no concept of a document to render into.

// package.json
{
  "scripts": {
    "test": "vitest"
  }
}

Worth knowing before your first run, since it can be surprising if you're expecting Jest's default one-shot behavior: vitest watches for changes by default — running npm run test doesn't run once and exit the way jest alone does; it stays running, re-executing affected tests as files change. If you specifically want a single, non-watching run — the mode you'd actually want in most CI pipelines — you'd reach for vitest run instead of the bare vitest command.

Your first test

// app/page.tsx
import Link from "next/link";

export default function Page() {
  return (
    <div>
      <h1>Home</h1>
      <Link href="/about">About</Link>
    </div>
  );
}
// __tests__/page.test.tsx
import { expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import Page from "../app/page";

test("Page", () => {
  render(<Page />);
  expect(screen.getByRole("heading", { level: 1, name: "Home" })).toBeDefined();
});

Notice this uses the exact same React Testing Library APIs (render, screen.getByRole) as the Jest article in this series — Vitest and Jest share a near-identical testing API surface by deliberate design; the actual difference between them lives almost entirely in the underlying runner and its configuration, not in how you write assertions or queries day to day. If you already know Jest, most of what you already know transfers directly.

One structural flexibility worth knowing: while __tests__ is the conventional location shown here, test files can just as easily live colocated directly inside app/, next to the components they test — Vitest doesn't enforce one specific layout, and colocating tests alongside source is a genuinely popular alternative for teams who prefer keeping a component and its test in visual proximity rather than in a separate mirrored directory tree.

Running your tests

npm run test

Remember this runs in watch mode by default per the note above — for a single pass (CI, a pre-commit hook, anywhere you specifically don't want a long-running watcher), use vitest run explicitly instead.

Key Takeaways

TaskCommand / config
Quickstartnpx create-next-app@latest --example with-vitest
Install (TS)Add vite-tsconfig-paths for path-alias resolution
Install (JS)Skip vite-tsconfig-paths — no tsconfig.json to read
DOM environmentenvironment: 'jsdom' — Vitest defaults to node, which has no DOM
Default run modeWatches continuously — use vitest run for a single pass
Async Server ComponentsNot supported — use E2E testing instead
Testing APINearly identical to Jest — render, screen.getByRole, etc.

Vitest's real pitch relative to Jest isn't a different testing philosophy — the actual test-writing experience is close to identical — it's underlying execution speed, particularly valuable as a suite grows into the hundreds or thousands of tests. If you're starting a genuinely new project with no existing Jest investment to preserve, and raw test-run speed matters to your workflow, Vitest is a reasonable default; if you're already deep in a Jest-based suite, the migration cost of switching runners is real and worth weighing against the speed gain before committing to it.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper