ScreenshotNeo

BlogHow-to

How to Use Percy with a Monorepo and Multiple Web Apps

Set up Percy snapshots for multiple apps in one monorepo with Cypress, clear project ownership, and CI jobs that route each run to the right Percy project.

By the ScreenshotNeo team4 October 202610 min read

Short answer: Give each app an explicit test command, base URL, Percy project token, and CI owner. For Cypress, install the Percy CLI and Cypress SDK, import the SDK in Cypress support, call cy.percySnapshot() after the app reaches a stable state, and run that app’s tests with npx percy exec -- cypress run. In CI, expose the matching project’s token as PERCY_TOKEN only to that app’s job.

Percy documents this Cypress setup and token-based project association. The reviewed Percy sources do not establish a universal rule for whether multiple apps should share one Percy project, or a current cross-framework recipe for coordinating parallel multi-app builds. Choose project boundaries deliberately and verify any parallel-build mechanism against the current CLI and account configuration.

1. Map the monorepo before adding Percy

Start with an inventory. This makes it clear which application a test job serves and which Percy project should receive its snapshots.

App Workspace / directory Test command Base URL Percy project secret CI owner
Storefront apps/storefront cypress run Its test server URL PERCY_TOKEN_STOREFRONT Storefront team
Admin apps/admin cypress run Its test server URL PERCY_TOKEN_ADMIN Admin team

These names are examples for your repository, not Percy-specific variables. The job can map its app-specific secret to the standard PERCY_TOKEN variable at runtime. Keep real project tokens in your CI secret store; do not commit them.

Decide whether apps share a Percy project

Percy associates a run with a project token. The sources reviewed here do not prescribe the project count for a monorepo, so treat this as a design decision to check in your Percy account and current CLI setup.

Design question Separate app projects can help when… A shared project may fit when…
Baselines Each app needs an independent visual baseline. The team deliberately wants one shared visual baseline and approval lifecycle.
Review ownership Different teams own review and approval for each app. The same reviewers own all apps’ visual changes.
Tokens and routing Explicit app-to-project mapping makes accidental cross-attribution less likely. The team has intentionally chosen one project and can distinguish the snapshots it contains.
Names and failures Separate projects make app ownership apparent in reviews and CI results. One project is acceptable if names and CI job context make app ownership clear.
Parallel work You can validate each app’s build behavior independently. You have confirmed how the current Percy CLI and account handle your intended parallel runs.

These are practical engineering trade-offs, not Percy rules established by the reviewed sources. Do not assume that sharing a token, project, or build automatically coordinates concurrent jobs; confirm the behavior supported by your installed CLI and account.

2. Install the Percy Cypress SDK and CLI

Percy’s Cypress setup uses @percy/cli and @percy/cypress. Install them in the workspace that owns the Cypress suite, or at the monorepo root if that matches your package-manager and workspace conventions.

npm install --save-dev @percy/cli @percy/cypress

Then load the integration from Cypress support code. With a typical Cypress support entry point:

// cypress/support/e2e.js (or your configured support file)
import '@percy/cypress'

Use the actual support file configured by your Cypress project. Package hoisting and workspace installation details depend on the monorepo’s package manager; the Percy setup guide does not define a package-manager-specific workspace recipe.

3. Add stable snapshots to each app’s Cypress tests

Drive the app to a deterministic state, verify that the state is ready, and then take a named snapshot. This example assumes your storefront’s Cypress configuration supplies the correct baseUrl and your test server is running.

// apps/storefront/cypress/e2e/product.cy.js
describe('Storefront product page', () => {
  it('shows a product with a selected size', () => {
    cy.visit('/products/example-product')

    // Wait for a meaningful, stable UI condition rather than an arbitrary delay.
    cy.get('[data-testid="product-details"]').should('be.visible')
    cy.get('[data-testid="product-price"]').should('contain.text', '$')

    cy.percySnapshot('Storefront - product page - default')

    cy.get('[data-testid="size-medium"]').click()
    cy.get('[data-testid="selected-size"]').should('contain.text', 'Medium')

    cy.percySnapshot('Storefront - product page - medium selected')
  })
})

Repeat the pattern in each app’s own suite. The Percy Cypress integration supports cy.percySnapshot([name][, options]); a descriptive name helps reviewers recognize the page and state. The default name is based on the test title, but explicit names are useful when a test captures several states.

  • Control data: use fixtures or otherwise predictable test data so content does not drift between runs.
  • Wait for the page: assert that relevant content or loading indicators have settled before the snapshot.
  • Avoid volatile pixels: timestamps, randomized content, rotating banners, and animations can create noisy diffs. Stabilize or disable them in the test environment where appropriate.
  • Keep coverage focused: prioritize critical pages and meaningful component states rather than capturing every possible combination.
  • Name for review: include app, page, and state when project organization alone does not make them obvious.

These practices align with Percy’s visual testing guidance. Review baseline changes deliberately and approve only changes that the team intends to keep.

4. Run the suite through Percy

For an app whose Cypress command is cypress run, the documented Percy wrapper is:

npx percy exec -- cypress run

Run it from the app workspace, or configure the package script to select that workspace. A minimal app-level package.json example is:

{
  "scripts": {
    "test:e2e:percy": "percy exec -- cypress run"
  }
}

Provide that process with the relevant app project’s token as PERCY_TOKEN. Without a project token and the Percy wrapper, the Cypress SDK can disable Percy snapshots instead of uploading them.

Example CI job structure

Keep one job per app when you want clear ownership and failure attribution. The following GitHub Actions fragment illustrates the mapping; replace the install, server-start, and readiness commands with those used by your repository. Store each token as a CI secret and scope it to the matching job.

jobs:
  storefront-percy:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: apps/storefront
    env:
      PERCY_TOKEN: ${{ secrets.PERCY_TOKEN_STOREFRONT }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
          cache-dependency-path: package-lock.json
      - run: npm ci
      - run: npm run build
      - run: npm run start:test &
      - run: npx percy exec -- cypress run

  admin-percy:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: apps/admin
    env:
      PERCY_TOKEN: ${{ secrets.PERCY_TOKEN_ADMIN }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
          cache-dependency-path: package-lock.json
      - run: npm ci
      - run: npm run build
      - run: npm run start:test &
      - run: npx percy exec -- cypress run

This is an orchestration pattern, not a Percy-published monorepo workflow. A background server command alone may not guarantee the app is ready before Cypress starts; use your repository’s established server/readiness mechanism. In a monorepo, root-level installation and package scripts may be more appropriate than running npm ci inside each app. Adjust the fragment to match the lockfile and workspace configuration you actually use.

5. Review and approve each app’s Percy build

When a run completes, review the Percy build associated with the token used by that job. Confirm that the snapshots belong to the expected app, inspect visual changes in context, and approve only intended changes. Clear names help when a project contains snapshots for more than one app.

Before enabling all app jobs, run one app and confirm the chain end to end: the CI job uses its intended token, Cypress reaches the intended base URL, named snapshots upload, and the resulting build appears in the expected Percy project. Then repeat for each app.

6. Handle assets served from another hostname carefully

If an app serves images, fonts, or other assets from a separate hostname, Percy has a historical changelog example using an allowed-hostnames setting under agent.asset-discovery:

# Historical, version-qualified example only
version: 1
agent:
  asset-discovery:
    allowed-hostnames:
      - cdn.example.com

The changelog says this syntax requires @percy/agent v0.10.0 or later. This is a legacy, version-qualified example, not a guarantee that the same configuration is current. Check the current Percy CLI and SDK documentation for your installed versions before relying on it. [Percy changelog: capturing assets from multiple hostnames]

7. Plan for parallel jobs and app-level failures

Separate CI jobs can make it easier to see which app failed, but they do not by themselves establish how Percy coordinates multiple builds or shards. The reviewed sources do not provide a current universal rule for coordinating parallel multi-app builds. Verify the supported build and parallelization mechanism for your installed CLI and account before depending on it.

A 2020 Percy changelog says Ember SDK v2 added more straightforward support for parallel builds and global configuration. That statement applies to that Ember SDK release; it is not proof of current cross-framework or general monorepo behavior. [Percy changelog: Ember SDK v2]

8. Troubleshooting Percy in a monorepo

Symptom Likely cause What to check or fix
No Percy build or snapshots appear. The test command ran without the Percy wrapper, or the job did not receive a valid PERCY_TOKEN. Confirm the command is npx percy exec -- cypress run and that the app job maps the correct secret to PERCY_TOKEN.
Snapshots appear under the wrong project. The CI job received another app’s token. Inspect the job’s secret-to-environment mapping and keep the app-to-token table in sync with CI.
Cypress cannot find cy.percySnapshot(). The Cypress support file did not load the Percy integration, or the wrong support file was edited. Check the configured Cypress support entry point and ensure it imports @percy/cypress.
The app is blank or half-loaded in snapshots. The server was not ready, the test navigated to the wrong base URL, or the test captured before the UI settled. Check the app’s base URL and server readiness, then wait for a meaningful UI condition before taking the snapshot.
Diffs change on every run. Dynamic data, timestamps, animations, or unsettled requests are affecting rendered output. Stabilize test data and page state; reduce coverage to intentional, reviewable states.
Assets from a CDN are missing. Asset discovery may not include the asset hostname, or an old config example may not match the installed version. Check current Percy configuration guidance for your CLI/SDK version. Treat the historical allowed-hostnames example as version-sensitive.
Parallel jobs produce confusing results. The coordination behavior for your CLI, SDK, and account has not been verified. Run a controlled test and consult current Percy documentation for the exact parallel-build mechanism before relying on it.
One app’s CI change breaks another app’s Percy setup. Shared scripts, config, or token wiring changed without an app-level ownership check. Make each job’s workspace, test command, base URL, and token mapping explicit; review shared configuration changes against every app.

9. Performance, reliability, and cost considerations

Keep runs bounded. Percy captures the states your tests request, so a focused set of important pages and states keeps review work manageable. Use stable selectors and explicit readiness checks to avoid capturing before the interface is ready.

Make CI failures attributable. App-level jobs and app-specific token mapping help engineers identify which suite produced a build or failed. This is an operational recommendation, not a Percy guarantee about monorepo topology.

Validate concurrency before scaling it. Parallel app jobs may fit your CI design, but the reviewed sources do not establish current coordination semantics across apps. Validate them with the installed Percy CLI and account before depending on concurrent builds or shards.

Check current plan limits and pricing directly. The research reviewed for this article does not establish Percy pricing, quotas, or plan limits, so this guide makes no cost estimate. Compare current account terms with the number of snapshots and review workflow your team needs.

Or skip the browser setup

If your task is to capture a webpage image or PDF rather than maintain Percy visual baselines and review diffs, ScreenshotNeo offers a website screenshot API and MCP server. One GET request can return a screenshot or PDF. See the ScreenshotNeo API documentation.

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 like a visitor, and known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Responses include X-Page-Verdict and X-Billed headers.
  • An MCP server gives AI agents tools for screenshots, page information, and PDF capture.
  • The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

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

FAQ

Does every app in a monorepo need its own Percy project?

The reviewed sources do not define a universal project topology. Choose based on baseline independence, ownership, review workflow, and verified account behavior.

Can I use Percy with multiple web apps in one repository?

The documented Cypress workflow can be applied per app by installing and loading the SDK, adding snapshots to that app’s tests, and running them through the Percy CLI with the intended project token.

Does this guide’s Percy setup replace Percy with ScreenshotNeo?

No. Percy is the visual testing workflow described here, with snapshots and review of visual changes. ScreenshotNeo is an option for standalone website screenshots and PDFs; use it when that capture job fits your need.

Sources