ScreenshotNeo

BlogHow-to

How to Use BackstopJS with Next.js

Set up BackstopJS to capture Next.js pages, compare them with approved visual baselines, and review changes locally or in CI.

By the ScreenshotNeo team4 October 20268 min read

BackstopJS captures screenshots of your Next.js pages and compares them with reference images you have approved. The practical loop is: start the app, define scenarios and viewports, capture references, run comparisons after changes, review the visual report, and approve only intentional differences. BackstopJS describes itself as a tool for visual regression testing that compares screenshots over time; it does not replace functional or end-to-end assertions.

This setup uses a local Next.js server and a stable route. BackstopJS scenarios require a label and URL, so making sure the app serves that URL during capture is implementation guidance inferred from its documented configuration, not a dedicated Next.js integration feature.

1. Install and initialize BackstopJS

From your Next.js project root, install BackstopJS as a development dependency and initialize its configuration and supporting files:

npm install --save-dev backstopjs
npx backstop init

Initialization can create or overwrite files. Review the generated files before accepting changes, especially if your repository already has a BackstopJS configuration. Check the installed package’s documentation when you need version-specific fields or engine options: BackstopJS README and usage guide and BackstopJS npm package documentation.

Add convenient project scripts to package.json (merge these into any existing scripts object):

{
  "scripts": {
    "visual:reference": "backstop reference",
    "visual:test": "backstop test",
    "visual:approve": "backstop approve"
  }
}

The commands can also be run directly with npx backstop reference, npx backstop test, and npx backstop approve.

2. Start the Next.js app and choose stable routes

A scenario URL must resolve when BackstopJS captures it. For a local run, start the app in one terminal and keep it running while you capture in another. Use the command that matches your project:

# Development server
npm run dev

# Or, for a production-like capture:
npm run build
npm run start

Use the URL and port printed by Next.js. The examples below assume http://localhost:3000. If your app uses another port, set the baseUrl accordingly. Before capturing, open each route in a browser and confirm it renders the intended state.

Prefer routes that produce the same visible state each time. Avoid transient loading screens, rotating content, current timestamps, randomized data, and animations where possible. If the page depends on authentication, cookies, or browser interactions, choose and configure the supported engine and state handling for your installed BackstopJS version.

3. Define scenarios and viewports

BackstopJS needs at least one viewport and each scenario needs a label and URL. Replace or adapt the scenarios and viewports in the generated backstop.config.js. This minimal example compares a home page and a product page at desktop and mobile widths:

module.exports = {
  id: "nextjs-visual-regression",
  viewports: [
    { label: "desktop", width: 1440, height: 900 },
    { label: "mobile", width: 390, height: 844 }
  ],
  scenarios: [
    {
      label: "Home page",
      url: "http://localhost:3000/",
      selectors: ["document"],
      delay: 500
    },
    {
      label: "Product page",
      url: "http://localhost:3000/products/example",
      selectors: ["document"],
      delay: 500
    }
  ],
  paths: {
    bitmaps_reference: "backstop_data/bitmaps_reference",
    bitmaps_test: "backstop_data/bitmaps_test",
    engine_scripts: "backstop_data/engine_scripts",
    html_report: "backstop_data/html_report",
    ci_report: "backstop_data/ci_report"
  },
  engine: "puppeteer",
  report: ["browser"],
  asyncCaptureLimit: 4,
  asyncCompareLimit: 50,
  debug: false,
  misMatchThreshold: 0.1,
  requireSameDimensions: true
};

This is a configuration example using common BackstopJS fields; keep any additional fields generated by your installed version if your workflow needs them. The threshold controls tolerated visual mismatch; it is not a universal definition of an acceptable change. Start with a strict threshold and adjust only when you understand the diffs it permits.

Choose scenario scope and viewports

  • Start with high-value routes: include pages where layout regressions matter to users, then add scenarios as your coverage needs grow.
  • Capture full pages or regions: selectors: ["document"] targets the whole page. For a focused comparison, use a stable CSS selector for a region, following the syntax supported by your installed version.
  • Represent supported layouts: include viewport sizes that exercise the breakpoints your app supports. A viewport is a browser window size, not a guarantee that every device renders identically.
  • Use descriptive labels: labels make scenarios easier to identify in reports and generated files.

Pick a browser engine for the coverage you need

BackstopJS documents Puppeteer and Playwright options. Playwright can select Chromium, Firefox, or WebKit and supports storage state for cookies and local storage. Use the engine and browser coverage that match your requirements, and confirm engine-specific configuration against the documentation for the version you installed. Do not assume different engines produce identical screenshots. See the BackstopJS project documentation.

4. Capture the approved reference images

With Next.js serving the configured URLs, capture the initial baseline:

npm run visual:reference

BackstopJS saves reference images under its configured reference path and uses them as the comparison target. Review the generated images before committing them. These files define what later tests consider expected, so commit the approved baseline with the code that it represents.

5. Compare after a change and review the report

After changing application code or styles, start the app in the state you want to test and run:

npm run visual:test

Inspect the browser report and its image comparisons. For every difference, decide whether it is an intended design change or a regression. Check the relevant viewport and page context; a small changed area can still cover a critical control or text.

If the change is intentional, approve the new screenshots:

npm run visual:approve

Approval replaces the reference images used for future comparisons. Review the changed baseline files and report in version control so a changed test oracle is visible in code review. Do not approve a diff simply to make a failing run pass.

6. Keep captures repeatable

  • Wait for a useful page state: a fixed delay can help with known client-side rendering, but it can also slow captures and may be too short or too long. Prefer a readiness condition supported by your installed BackstopJS version when timing is variable.
  • Stabilize test data: use predictable content and avoid showing dates, random values, or rotating banners in the captured state.
  • Handle fonts and images: confirm they load before capture. A missing font or late image can create widespread diffs even when layout code is unchanged.
  • Control animation: animations and carousels can produce captures at different frames. Disable or pause them in the test environment when appropriate.
  • Check route and server readiness: a browser must be able to reach the exact scenario URL. Make sure the server remains running and the route does not redirect to an unexpected page.

These are practical steps for making browser captures repeatable. The official Next.js testing guide provides broader testing context, but does not describe a dedicated BackstopJS integration: Next.js testing guide.

7. Run visual checks in CI

BackstopJS lists CI and source-control support and offers JUnit reporting. The exact pipeline syntax depends on your CI provider and BackstopJS version. At a minimum, configure the job to install dependencies, build and start the app, wait until the target URL responds, run backstop test, and retain the report and diff images when the job fails.

Keep the same routes, test data, browser setup, and reference images between local and CI runs. If the same page renders differently across environments, BackstopJS documents Docker execution as an option for reducing rendering variation, especially text differences. Docker is a mitigation; it does not guarantee that all differences disappear. Factor in Docker availability and image maintenance. See the BackstopJS README for current Docker and reporting options.

8. Troubleshooting

Symptom Likely cause What to check or change
Navigation or connection error The app is stopped, the port is wrong, or the route is unavailable. Start Next.js, use the actual local port in each scenario URL, and open the route directly before running BackstopJS.
Redirect or login page captured The route requires authentication or redirects under the current browser state. Confirm the expected URL and auth flow. If the page needs cookies or local storage, check the Playwright storage-state options supported by your installed version.
Many diffs after an unrelated code change Fonts, images, data, animation, or rendering timing changed between captures. Inspect the diff and test environment, wait for the intended ready state, stabilize content, and compare on a consistent runtime.
Text differs on CI but not locally Browser or operating-system rendering differs across environments. Align the runtime and browser configuration. Try documented Docker mode to reduce environment variation, then verify the resulting captures.
Reference capture is blank or incomplete The route was not ready, assets were missing, or client rendering had not finished. Check the page in a browser, verify asset requests and server logs, then configure an appropriate wait or readiness condition for your BackstopJS version.
Approval changes many baseline files The capture environment or page state differs broadly from the prior reference. Review the report and file changes before committing. Confirm the selected viewport, route, data, browser engine, and runtime are intentional.
Unknown configuration option or engine error The example or a copied option does not match the installed BackstopJS version. Check the installed package’s version-matched documentation and generated configuration; engine-specific options can differ.

9. Performance, reliability, and maintenance

Capture time grows with the number of scenario and viewport combinations, page load time, and any waits you configure. Begin with the routes and layouts that matter most, then expand coverage based on risk. Parallel capture limits can affect runtime and resource use; adjust them to suit the machine running the job and check the version’s documentation for the supported fields.

Visual checks are reliable only when the app state and capture environment are controlled. A passing comparison says the screenshots are sufficiently similar under the configured comparison rules; it does not prove that a button works, a route is accessible, or business logic is correct. Keep functional assertions in your functional or end-to-end test suite.

Reference images are part of the test oracle. Store and review them with source changes, and treat approvals as deliberate updates. BackstopJS does not establish a universal scenario count, threshold, or compatibility matrix for all Next.js, Node.js, browser, and Docker versions; check your installed versions when upgrading.

Or skip the browser setup

If you need screenshots for a page without maintaining a local browser capture and baseline workflow, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its one-call API returns an image or PDF, and its documentation covers the request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Create a free account and get 1,000 screenshots a month with no card.

FAQ

Does BackstopJS test whether a Next.js page works?

It checks captured appearance against reference images. Use functional or end-to-end tests to check behavior such as form submission, navigation, and data updates.

Do I need a special Next.js plugin?

The workflow here uses BackstopJS scenarios pointed at a running Next.js route. The cited documentation establishes the scenario URL workflow, not a special Next.js plugin or integration.

Should I approve every failed visual test?

No. Approve only after reviewing the difference and confirming the changed appearance is intended.

Will Docker make local and CI screenshots identical?

Docker can reduce rendering variation across environments, but the BackstopJS documentation does not claim it removes every source of difference.