Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If a URL opens in your browser but cy.visit() reports “page not found,” do not assume Cypress is broken. First compare the exact request Cypress made with the address you typed manually, then inspect its status code, redirects, authentication state, and server routing. The usual causes are a different URL (often a slash or base-path difference), a protected route, a single-page-app history route without a production fallback, or an environment-specific proxy problem.

Start with the request Cypress actually made

A browser can finish on a page that looks correct after redirects, client-side navigation, or an existing login session. Cypress reports what happened during its own request. Capture that request and response before changing configuration.

1. Compare the complete URL

Print or inspect the final URL, including protocol, hostname, port, path, query string, hash, and trailing slash. Compare it character-for-character with the address-bar URL that works.

describe('route diagnostic', () => {
  it('visits the intended URL', () => {
    cy.visit('/inventory.html')
    cy.url().then(url => cy.log(`Cypress URL: ${url}`))
  })
})

Check the baseUrl in cypress.config.js (or the equivalent TypeScript configuration) and the path passed to cy.visit():

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    baseUrl: 'https://example.test'
  }
})

A relative path is resolved against baseUrl. A leading slash replaces the path portion of that base URL, while a path without a leading slash is resolved differently. Make the intended result explicit and avoid accidental double slashes or missing subpaths.

2. Inspect status codes and redirects

Use the browser’s Network panel, your server access log, or a command-line request to see the response for the exact Cypress URL. A manually entered address may redirect to the site root, login page, or another route before rendering something that appears successful. A 404 for the original path can therefore be hidden by the final page.

cy.request({
  url: '/inventory.html',
  failOnStatusCode: false
}).then(response => {
  cy.log(`status: ${response.status}`)
  cy.log(`redirected: ${response.redirected}`)
  expect(response.status).to.be.oneOf([200, 301, 302, 307, 308])
})

cy.request() is a diagnostic aid; it does not replace a real browser visit when you need to test rendering. If the response is a redirect, follow the chain and determine whether the destination is expected.

Branch by the failure you observe

The URL differs from the manual visit

  • Correct the baseUrl, environment variable, port, or path spelling.
  • Check whether the application is mounted under a prefix such as /app.
  • Decide whether the trailing slash is significant for your server or CDN and use one canonical form.
  • Verify that the test is running in the same environment (local, staging, or production) as the manual check.

For example, if the working address is https://example.test/app/inventory.html, a baseUrl of https://example.test combined with cy.visit('/inventory.html') requests the wrong location.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The route redirects to login

A protected page can be reachable manually because your browser already has a session. Cypress starts with a separate browser profile, so the same URL may redirect to a login page or return an authorization error. Establish authentication before visiting the protected route, then assert the result.

cy.session('test-user', () => {
  cy.visit('/login')
  cy.get('[name=email]').type(Cypress.env('E2E_EMAIL'))
  cy.get('[name=password]').type(Cypress.env('E2E_PASSWORD'), { log: false })
  cy.get('button[type=submit]').click()
  cy.url().should('include', '/dashboard')
})

cy.visit('/inventory.html')
cy.url().should('include', '/inventory.html')

Use your application’s supported login flow or a documented API-based setup. Do not copy real session cookies or credentials into source control. If authentication is not the cause, remove this branch from your diagnosis rather than adding unnecessary login code.

The app uses history-mode client routing

In a single-page application, a route such as /todos/42 may be resolved by the client router after index.html loads. A development server often supplies that fallback automatically. A simple production static server may instead look for a physical todos/42 file and return 404. Configure the production server, reverse proxy, or CDN to serve index.html for non-file routes; then let the client router resolve the path.

Do this only when the application uses history-style routing. Hash routes (for example, /#/todos/42) are requested as the same entry document and usually do not require the same server fallback. After changing the server, test a direct request to the deep link—not only a click from the home page.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The failure occurs only on localhost or one browser

An older Cypress issue described intermittent 404s while visiting different localhost ports with Cypress 3.0.1 on Windows 10 and Chrome. A later comment attributed a similar symptom to Chrome bypassing Cypress’s proxy for loopback addresses and mentioned --proxy-bypass-list=<-loopback> as a workaround. This is historical, version-specific evidence, not a current universal fix.

  1. Record the Cypress version, browser version, operating system, and every localhost port.
  2. Reproduce with the current Cypress and browser versions.
  3. Compare the request in Cypress with a direct command-line or browser request.
  4. Only then test a proxy setting in an isolated run, and document how to remove it.

Do not add a proxy flag merely because the error message resembles that old report.

Understand base paths and full-page navigation

Every framework has a base URI or equivalent setting used to resolve relative links. A relative URL can therefore work in one context and fail in another if the app is deployed below a subdirectory. Client-side navigation may never contact the server for a route, while a forced full-page load does. Use the framework’s documented base-path setting consistently in the build, server, and Cypress configuration.

When diagnosing, test both transitions:

// Client-side navigation from the entry page
cy.visit('/')
cy.get('a[href="/todos/42"]').click()
cy.url().should('include', '/todos/42')

// Direct server request to the same route
cy.visit('/todos/42')

If the click works but the direct visit fails, the server fallback or deployment base path is the leading suspect.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A repeatable diagnostic checklist

  1. Save the exact cy.visit() argument and resolved baseUrl.
  2. Record the requested URL, status code, response headers, and redirect locations.
  3. Check whether the manual browser has a login session that Cypress lacks.
  4. Determine whether the route is a server file, an API endpoint, or a client-side history route.
  5. For an SPA route, verify that the production layer returns the entry document for a direct deep-link request.
  6. Compare local and remote hosts, ports, schemes, and proxy settings.
  7. Re-run with current Cypress and browser versions before applying old workarounds.
  8. Add a focused assertion so the test fails with the actual destination instead of a generic page-not-found message.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common errors and targeted fixes

What you observe Likely explanation Next action
Manual page is visible, Cypress URL ends at the root Redirect from the requested path Inspect redirect responses and authentication requirements
Manual visit works only while logged in Cypress has no matching session Create a test session, then assert the post-login URL
Clicking a route works; direct cy.visit() returns 404 Missing history-mode fallback Serve the SPA entry document for unknown non-file paths
Only a subdirectory deployment fails Inconsistent base path Align build, server, links, and baseUrl
Only one old Chrome/Cypress/localhost combination fails Environment-specific proxy behavior Reproduce on current versions before considering a proxy flag
Status is 200 but the page is blank JavaScript or asset failure after the document loads Inspect console and asset requests; this is not a simple 404 diagnosis

Reliability and performance considerations

Keep URL and response diagnostics in a small, dedicated test or setup command so normal suites remain readable. Prefer a stable staging hostname over a changing localhost port when testing routing. If a page depends on asynchronous redirects or client rendering, wait for a specific element that proves the intended route loaded instead of relying only on a short timeout.

Use cy.intercept() to observe relevant API calls, but do not mistake a successful API response for a successful document request. A server can return healthy data while the HTML route still returns 404. Conversely, a valid HTML response can be followed by a failed JavaScript bundle request.

cy.intercept('GET', '**/api/**').as('api')
cy.visit('/todos/42')
cy.wait('@api')
cy.get('[data-testid="todo-page"]').should('be.visible')

Or skip the browser setup

If your goal is to generate a clean image or PDF of a URL rather than test Cypress routing, ScreenshotNeo provides a single HTTP request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

cURL:

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

Python:

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)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for all 63 options, including full-page and element capture, device and viewport settings, JavaScript and CSS, waits, request blocking, cookies and headers, geolocation, PDFs, signed links, asynchronous jobs, bulk capture, caching, and usage reporting. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Why does Cypress show a 404 when the browser displays the app shell?

The browser may have followed a redirect, used an existing login session, or loaded the SPA entry document before client-side routing. Inspect the original Cypress response and redirect chain to distinguish these cases.

Should I disable web security or add a proxy flag?

Not as a first step. Those settings address particular cross-origin or historical localhost conditions. Confirm the URL, response, authentication, routing fallback, and current software versions first.

How can I prove that the server supports a deep link?

Request the deep-link URL directly in a fresh session or with a command-line HTTP client and verify that the server returns the application entry document rather than a 404. Then run the same URL with cy.visit().

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.