The Post-Migration Testing Dilemma
Transitioning a project from Vite to Next.js is a significant architectural shift. While your React components might stay largely the same, the environment they run in changes drastically. You move from a purely Client-Side Rendered (CSR) world to one dominated by Server-Side Rendering (SSR) and the hybrid capabilities of the App Router.
One of the biggest questions developers face after a migration is: "What happens to my tests?" If you were using Vitest in your Vite project, the good news is that you don't have to switch to Jest. Vitest is perfectly capable of testing Next.js applications, but it requires specific configurations to handle Next.js APIs like next/navigation, next/image, and Server Components.
Why Keep Vitest with Next.js?
While Next.js documentation historically leaned toward Jest, Vitest has gained massive popularity for its speed and developer experience. If your codebase already has hundreds of tests written for Vitest, re-configuring it for Next.js is much more efficient than a full rewrite.
Vitest integrates seamlessly with Modern ESM, handles TypeScript out of the box, and provides a much faster feedback loop during development. However, because Next.js uses its own compiler (SWC), we need to ensure Vitest understands how to resolve Next-specific modules.
Step 1: Updating the Configuration
In a standard Vite project, your vitest.config.ts likely points to your Vite plugins. In Next.js, you need to ensure that Vitest handles the environment correctly. Here is a baseline configuration for a migrated Next.js project:
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'],
exclude: ['node_modules', '.next', 'dist'],
},
});
Note the inclusion of vite-tsconfig-paths. Next.js heavily relies on path aliases (like @/*). This plugin ensures Vitest can resolve those imports just as Next.js does.
Step 2: Handling Next.js Features
Migration often breaks tests because components now rely on Next.js-specific hooks. If you used a tool like ViteToNext.AI to automate your transition, your components are already updated to Next.js patterns, which means your test suite now needs to account for things like useRouter or useSearchParams.
Mocking the Router
The most common failure occurs when a component calls useRouter(). Since Vitest runs in a Node/JSDOM environment without the Next.js context provider, you must mock these calls. Update your vitest.setup.ts file:
import '@testing-library/jest-dom';
import { vi } from 'vitest';
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: vi.fn(),
replace: vi.fn(),
prefetch: vi.fn(),
back: vi.fn(),
}),
usePathname: () => '/',
useSearchParams: () => new URLSearchParams(),
}));
Mocking Next/Image
The next/image component performs optimizations that aren't necessary for unit tests. To avoid errors related to missing providers or loader issues, add this to your setup:
vi.mock('next/image', () => ({
__esModule: true,
default: (props: any) => {
return <img {...props} />;
},
}));
Step 3: Managing Server Components
Next.js introduces Server Components (RSC) by default in the App Router. Vitest is primarily designed for client-side component testing using tools like React Testing Library. Testing an actual Server Component (an async component) usually requires an integration test or an E2E tool like Playwright.
However, you can unit test the logic inside Server Components by abstracting the business logic into separate, pure functions. For the components themselves, ensure you are only using Vitest for those marked with the 'use client' directive.
Step 4: Environment Variables
Next.js loads environment variables from .env.local and prefixes client-side variables with NEXT_PUBLIC_. Vitest doesn't load these by default. You can use the dotenv package in your config or manually define them in your test setup:
process.env.NEXT_PUBLIC_API_URL = 'http://localhost:3000';
Debugging Common Issues
- Transform Errors: If Vitest fails to parse a file, it's often because a node module is using ESM in a way that JSDOM dislikes. Use the
deps.inlineproperty in your config to force Vitest to process specific libraries. - CSS Imports: Next.js uses CSS Modules and global CSS differently. Vitest might complain about
.cssimports. You can use a mock to ignore these during testing.
Conclusion
Migrating from Vite to Next.js doesn't mean you have to abandon your Vitest setup. By updating your config to handle path aliases, mocking the Next.js navigation stack, and handling image optimizations, you can keep your existing test suite healthy and fast. The key is simulating the environment Next.js provides while keeping the lightweight nature of Vitest.
Further reading: ViteToNext.AI Migration Guide
Top comments (0)