How to Test a Next.js App (Unit and E2E)

Next.js testing follows a simple pyramid: many fast unit tests, fewer integration tests, a small set of E2E tests for the journeys that must never break. Most teams get this backwards — they skip unit tests and try to cover everything with slow E2E. Here's the layered approach that keeps CI fast and catches real bugs.
Quick Answer
Test a Next.js app in layers: unit tests for components and pure logic (Vitest or Jest with Testing Library), a few integration tests for data flows, and end-to-end tests for critical user journeys (Playwright). Run them in CI on every push. Aim for many fast unit tests, fewer integration tests, and a small set of high-value E2E tests covering signup, login, and payment.
The testing pyramid for Next.js
Layer | Tool (common) | What it covers |
|---|---|---|
Unit | Vitest or Jest + Testing Library | Components, hooks, pure functions |
Integration | Vitest/Jest | Multiple units + data access together |
End-to-end | Playwright | Real user journeys in a browser |
Keep the base wide (fast unit tests) and the top narrow (a few critical E2E flows).
Unit tests
Test components and logic in isolation. Render a component, assert what the user sees, and test pure functions directly.
// example.test.ts (untested-here)
import { describe, it, expect } from 'vitest';
import { formatPrice } from '@/lib/price';
describe('formatPrice', () => {
it('formats cents to a currency string', () => {
expect(formatPrice(1999)).toBe('$19.99');
});
});Integration tests
Test a few units together — for example, a function that reads from the database and shapes a response — against a test database or mocked data layer.
End-to-end tests with Playwright
E2E tests drive a real browser through the journeys that must never break: sign up, log in, and complete a payment. Keep them few but meaningful, since they are slower than unit tests.
// e2e/login.spec.ts (untested-here)
import { test, expect } from '@playwright/test';
test('user can log in', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page).toHaveURL(/dashboard/);
});Run them in CI
Run unit and integration tests on every push and E2E on pull requests or before deploy, so regressions are caught automatically.
How this maps to FastStaq
FastStaq is a TypeScript monorepo (client/server/shared), so the layered approach above applies directly: unit-test shared logic and components, integration-test the API and data layer, and reserve E2E for the critical flows. Testing here is general best practice applied to that stack. See Playwright tutorial and how to build API routes in Next.js.
Frequently asked questions
Vitest or Jest? Both work well with Next.js and TypeScript; Vitest is fast and modern, Jest is established. Pick one and stay consistent.
How many E2E tests should I write? Few but high-value — cover signup, login, and payment, not every page.
Where do tests run? Locally and in CI on every push, with E2E before deploy.
Next steps
Read the Playwright tutorial
Back to the Supabase + Next.js guide


