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

Next.js Setting up Cypress

Of the four testing tools covered in this series, Cypress is the one that does double duty out of the box — genuine End-to-End testing and component-level testing, in one tool, with a visual, interactive test runner that's arguably its biggest practical advantage over the others. This article covers setting both modes up in a Next.js App Router project, plus the one real limitation worth knowing before you commit to it for component testing specifically.

The fastest path: a pre-configured example

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

This scaffolds an entire project with Cypress already wired in — worth using directly if you're starting fresh and want a known-working reference configuration rather than assembling one from scratch.

Setting it up manually

npm install -D cypress

Add the interactive runner as an npm script:

// package.json
{
  "scripts": {
    "cypress:open": "cypress open"
  }
}

Running it for the first time is itself part of the setup flow, not just "the test runner" — cypress open walks you through choosing E2E Testing, Component Testing, or both, and automatically generates a cypress.config.js file plus a cypress/ folder based on what you select.

One version constraint worth flagging directly, since it's the kind of error that looks like a config mistake rather than a version mismatch: Cypress versions below 13.6.3 don't support TypeScript 5 with moduleResolution: "bundler". If you're on an older Cypress version and hitting confusing TypeScript resolution errors specifically around this setting, upgrading Cypress itself — not touching your tsconfig.json — is very likely the actual fix.

Writing your first E2E test

Confirm your config looks like this:

// cypress.config.ts
import { defineConfig } from "cypress";

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {},
  },
});

With two simple pages to test navigation between:

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

export default function Page() {
  return (
    <div>
      <h1>Home</h1>
      <Link href="/about">About</Link>
    </div>
  );
}
// app/about/page.js
import Link from "next/link";

export default function Page() {
  return (
    <div>
      <h1>About</h1>
      <Link href="/">Home</Link>
    </div>
  );
}

The test itself reads like a script of actual user behavior — because that's precisely what E2E testing is meant to simulate:

// cypress/e2e/app.cy.js
describe("Navigation", () => {
  it("should navigate to the about page", () => {
    cy.visit("http://localhost:3000/");
    cy.get('a[href*="about"]').click();
    cy.url().should("include", "/about");
    cy.get("h1").contains("About");
  });
});

Running E2E tests against a real build, not dev mode

This is worth taking seriously rather than skipping for convenience: Cypress genuinely drives a browser against your running application, and the recommendation is to test against your production build, not the dev server — dev mode's behavior (HMR, unoptimized bundles, different error handling) doesn't perfectly mirror what real users actually experience, and a test suite that only ever ran against next dev can pass locally while missing production-specific issues.

npm run build && npm run start

Then, in a separate terminal:

npm run cypress:open

Two small conveniences worth adopting rather than typing http://localhost:3000 in every single test: set baseUrl: 'http://localhost:3000' in cypress.config.js so you can write the shorter cy.visit('/') throughout your suite, and consider the start-server-and-test package, which lets one script boot the production server, wait for it to actually be ready, and then run Cypress — genuinely useful for CI, where you can't rely on a human manually starting two terminals in the right order.

Component testing: a genuinely different, lighter mode

Component tests mount one specific component in isolation — no full application bundle, no running server required — which makes them meaningfully faster to run than a full E2E suite, at the cost of testing something narrower.

Select Component Testing in the Cypress app and choose Next.js as the framework; this generates a cypress/component folder and updates your config automatically:

// cypress.config.ts
import { defineConfig } from "cypress";

export default defineConfig({
  component: {
    devServer: {
      framework: "next",
      bundler: "webpack",
    },
  },
});
// cypress/component/about.cy.tsx
import Page from "../../app/page";

describe("<Page />", () => {
  it("should render and display expected content", () => {
    cy.mount(<Page />);
    cy.get("h1").contains("Home");
    // Following the link itself is better suited to an E2E test than a component test
    cy.get('a[href="/about"]').should("be.visible");
  });
});

That comment in the test isn't incidental — it's the actual philosophical line between the two testing modes worth internalizing: a component test verifies this component renders correctly given its props/state; whether clicking a link inside it actually navigates somewhere is a cross-component, cross-page concern that belongs in E2E testing instead, not something a component test should try to verify itself.

Two limitations worth knowing before you lean on component testing heavily: Cypress currently doesn't support Component Testing for async Server Components — the same ecosystem-wide gap covered in this series' testing overview article, and the same fix applies: use E2E testing for those specifically. And because component tests deliberately don't spin up a real Next.js server, features that assume one exists — <Image />'s optimization endpoint being the most common example — may not function correctly out of the box in this mode.

Running headlessly, for CI

Interactive cypress open is great for local development and debugging, but CI environments need the headless equivalent:

// package.json
{
  "scripts": {
    "e2e": "start-server-and-test dev http://localhost:3000 \"cypress open --e2e\"",
    "e2e:headless": "start-server-and-test dev http://localhost:3000 \"cypress run --e2e\"",
    "component": "cypress open --component",
    "component:headless": "cypress run --component"
  }
}

cypress run is the headless counterpart to cypress open — same tests, no interactive UI, exactly the mode you want a CI pipeline actually invoking.

Key Takeaways

TaskCommand / config
Quickstartnpx create-next-app@latest --example with-cypress
Install manuallynpm install -D cypress, then cypress open to scaffold config
E2E test locationcypress/e2e/*.cy.js
Component test locationcypress/component/*.cy.tsx
Run E2E against productionnpm run build && npm run start, then cypress open
Headless (CI)cypress run --e2e or cypress run --component
Known gapNo component testing support for async Server Components — use E2E instead

Cypress's real strength in a Next.js project is consolidating two testing modes — E2E and component — into one tool with a genuinely excellent interactive debugging experience, at the cost of one specific, known gap around async Server Components that the whole testing-tool ecosystem currently shares. If that gap doesn't affect much of your codebase, or you're comfortable routing exactly that slice to E2E tests, Cypress alone can reasonably cover both layers of your test suite.

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