ScreenshotNeo

BlogGuides

How to Test Next.js Applications

Build a practical Next.js testing strategy with unit, component, integration, and browser tests using Jest, Vitest, Playwright, and Cypress.

By the ScreenshotNeo team4 October 20269 min read

Test a Next.js application in layers: use unit tests for isolated logic, component tests for rendered UI and interactions, integration tests for boundaries between modules, and end-to-end (E2E) tests for important user journeys in a browser. Choose the test runner based on the behavior you need to protect: Next.js documents Jest and Vitest for unit testing, and Playwright and Cypress for browser testing. One important constraint: async Server Components are not fully supported by some unit and component testing tools, so cover their behavior with E2E tests.

This guide shows how to choose a test layer, set up the documented tools, run useful tests, and diagnose common failures. For exact commands and configuration, follow the current guide for your Next.js version and chosen runner; package and framework compatibility can change.

1. Choose the test layer that matches the risk

Test type What it checks Good fit
Unit An isolated function, hook, or small component Formatting, validation, branching logic, utility behavior
Component Rendered UI, props, and responses to user events Menus, dialogs, form feedback, client-side interactions
Integration Multiple units working together across a seam Data adapters, service boundaries, connected UI modules
End-to-end A user task through a running application in a browser Navigation, sign-in, form submission, loading and error flows
Snapshot Current rendered output compared with a saved representation Selective checks for intentional output changes

Begin with user-visible failures that matter: broken navigation, forms that do not submit, data pages that fail to load, and missing error or loading states. Keep isolated tests fast, test interactions at the component layer, and reserve browser tests for complete journeys and framework behavior that requires a running app.

A snapshot only tells you that output changed. Review and update it deliberately, and pair it with assertions about behavior when behavior is what matters.

2. Pick a runner: Jest, Vitest, Playwright, or Cypress

Tool Documented role in Next.js guidance Key consideration
Jest with React Testing Library Unit and snapshot testing next/jest configures the Next.js compiler transform and handles common styles, images, next/font, environment files, and .next exclusions. Jest does not currently support async Server Components in the documented setup.
Vitest Unit testing Use the current Next.js integration guide for setup details; do not assume Jest configuration applies unchanged.
Playwright E2E browser automation Supports Chromium, Firefox, and WebKit. Useful when browser coverage matters.
Cypress E2E and component testing Component testing has limitations with async Server Components and server-dependent features such as <Image />.

Choose Jest if the project already uses it or wants the Next.js integration for unit and snapshot tests. Choose Vitest if it fits the project’s unit-test workflow and you can follow its current Next.js guide. Choose Playwright when you want browser E2E coverage across its supported browser engines. Cypress offers E2E and component modes; account for its component testing constraints. Next.js recommends running E2E tests against production code in its Playwright and Cypress guidance, because this more closely exercises deployed behavior.

3. Make a practical test plan

  1. List critical user tasks. Write down the flows whose failure would block users, such as navigating to a data page, submitting a form, or seeing a useful error when a request fails.
  2. Test pure logic in isolation. Cover input boundaries and expected outputs with the unit runner already supported by the project.
  3. Test interactive UI at the component layer. Check what the user can see and do: labels, enabled states, validation feedback, and the result of an action.
  4. Test important module boundaries. Add integration coverage where bugs are likely to arise between a page, data access code, and other modules.
  5. Exercise critical journeys in a browser. Include navigation, successful and failed submissions, and meaningful loading or error states. Use E2E coverage for async Server Component behavior.
  6. Keep snapshots selective. Use them to flag significant rendered-output changes, not as a substitute for checking outcomes.

4. Set up and write a Jest unit test

Next.js documents Jest with React Testing Library for unit and snapshot testing. For the supported setup and current installation commands, use the Next.js Jest guide. Its next/jest integration configures common Next.js transforms and asset handling. A representative test of isolated logic looks like this:

// app/lib/format-status.ts
export function formatStatus(status: 'ready' | 'pending' | 'failed') {
  if (status === 'ready') return 'Ready';
  if (status === 'pending') return 'In progress';
  return 'Could not load';
}

// app/lib/format-status.test.ts
import { describe, expect, it } from '@jest/globals';
import { formatStatus } from './format-status';

describe('formatStatus', () => {
  it('maps each status to user-facing text', () => {
    expect(formatStatus('ready')).toBe('Ready');
    expect(formatStatus('pending')).toBe('In progress');
    expect(formatStatus('failed')).toBe('Could not load');
  });
});

Use the project’s configured Jest environment and scripts to run this test. If the code touches browser APIs, check whether its test environment provides those APIs or whether the code should be isolated behind a small adapter. For async Server Components, do not spend time forcing this unit-test setup to render them; cover their observable behavior in an E2E test.

5. Use Vitest for unit tests when it fits the project

Next.js lists Vitest as a unit-testing option and maintains a dedicated integration guide. Follow the current Next.js Vitest guide for installation, environment, and configuration. Do not copy a Jest configuration and assume transforms, aliases, or environment setup are identical. A small pure function test follows the same general pattern: arrange an input, call the function, and assert the returned result. Keep framework-specific setup aligned with the guide for the exact Next.js and Vitest versions in use.

6. Add browser tests with Playwright

Use Playwright for complete user flows. The Next.js guide documents either the with-playwright starter example or setting up with pnpm create playwright. See the Next.js Playwright guide for the current runnable project setup. A representative navigation test is:

// tests/navigation.spec.ts
import { test, expect } from '@playwright/test';

test('user can open the dashboard', async ({ page }) => {
  await page.goto('/');
  await page.getByRole('link', { name: 'Dashboard' }).click();
  await expect(page).toHaveURL(/dashboard/);
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

Configure the Playwright project’s base URL and server startup according to the guide. For behavior closer to deployment, run the suite against production code where feasible. Playwright supports Chromium, Firefox, and WebKit; start with the browser coverage your users need and expand it when cross-browser behavior is a risk.

7. Add E2E or component tests with Cypress

Cypress is documented by Next.js for both E2E and component testing. Use the Next.js Cypress guide for current setup and examples. Prefer E2E coverage for complete user paths and run against production code when feasible. Component tests are useful for UI behavior, but async Server Components are not currently supported in that mode; features that depend on the Next.js server, including some uses of <Image />, may need a server and may not work out of the box in component tests.

When using TypeScript with moduleResolution: "bundler", check Cypress compatibility. The Next.js guide notes that Cypress versions before 13.6.3 did not support that combination and that the issue was resolved in 13.6.3 and later. Verify current version compatibility before relying on this version-specific note.

8. Test async Server Components without fighting the tool

Async Server Components are the most important testing caveat in the current Next.js guidance. Some tools do not fully support them for unit or component tests. Next.js recommends E2E testing over unit testing for async components in the meantime. Test the rendered outcome and user-visible states in a browser rather than trying to make a unit runner execute server behavior it does not support.

This is a tool-support limitation, not a reason to skip tests. You can still unit-test pure helper functions used by server code and use browser tests to cover the component’s actual behavior. Recheck the current Next.js and test-runner documentation as support evolves.

9. Troubleshooting common failures

Symptom Likely cause What to do
Async Server Component cannot render in a unit or component test The selected test mode does not fully support async Server Components. Cover its behavior with E2E tests. Unit-test pure logic separately.
Jest fails to resolve styles, images, fonts, or Next.js imports Jest lacks the Next.js transforms and module handling. Use the documented next/jest integration and check the current Next.js Jest guide.
Component test fails around <Image /> or server-dependent behavior The component relies on Next.js server behavior unavailable in the component test setup. Use a running app and E2E test for that behavior, or isolate the UI behavior that can be tested independently.
Cypress reports a TypeScript module resolution issue An incompatible Cypress and TypeScript configuration/version combination. Check current Cypress compatibility; the Next.js guide identifies support from Cypress 13.6.3 for TypeScript 5 with bundler module resolution.
Browser test passes locally but fails in CI The test may depend on timing, external data, or environment assumptions. Make the test wait for a meaningful visible condition, control its test data where possible, and inspect the CI browser and server logs.
A snapshot changes unexpectedly Rendered output changed, or the test includes unstable output. Review the diff, assert behavior directly where possible, and update the snapshot only when the change is intended.

10. Performance, reliability, and maintenance

Unit tests are generally the quickest layer because they isolate small pieces of code; browser tests run more of the application and require a server and browser. Keep the bulk of routine checks focused and fast, then spend E2E time on journeys where browser behavior matters. This is a practical allocation, not a benchmark: actual runtime depends on the application, test count, and environment.

Reduce flaky tests by asserting user-visible outcomes instead of arbitrary timing, keeping test data predictable, and running browser tests against a production build when feasible. When a failure occurs, preserve enough logs and browser output to tell whether the cause is the application, test setup, or environment. Revisit tool and framework versions before debugging configuration that may have changed.

11. Capture screenshots for visual review

Browser tests can verify that a page or element is visible. For a saved visual artifact, you can capture a screenshot locally with browser tooling, or use a screenshot API when you need repeatable captures without maintaining browser setup. ScreenshotNeo is a website screenshot API and MCP server for developers. It returns PNG, JPEG, WebP, or PDF from one GET request. This is a screenshot capture option; it does not replace assertions or E2E tests.

Or skip the browser setup

One call captures a URL. See the ScreenshotNeo API docs for options and response details.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie banners are accepted and removed before capture; known consent platforms, newsletter popups, and chat widgets can also be removed, with each step configurable.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers say the page verdict and whether it was billed.
  • An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
  • 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for 1,000 free screenshots a month, with no card.

12. Frequently asked questions

Do I need both unit and E2E tests?

They cover different risks. Use unit tests for isolated logic and E2E tests for critical behavior across a running application.

Should every component have a snapshot?

No. Keep snapshots selective and review changes; use direct assertions when a specific behavior is the concern.

Can I test async Server Components with Jest?

The current Next.js Jest guidance says Jest does not support async Server Components and recommends E2E testing for them. Check the current guide as tool support changes.

Which browser runner should I start with?

Pick the one that fits the project and coverage need: Playwright for E2E across Chromium, Firefox, and WebKit, or Cypress for E2E and component modes with the documented component-test limitations.

Do screenshot captures prove the page is correct?

No. A screenshot is an artifact for visual inspection. Use assertions and user-flow tests to verify behavior.

Official references