SecurityError — pushState Cannot Be Called on a Cross-Origin URL
Fix pushState SecurityError by keeping history URLs same-origin and passing only path, query, and hash, never a full external URL.
Symptoms
- The same-origin check failed at the pushState call
- pushState works locally but throws behind a CDN or on a different host
- The error names a URL with a different origin than the document
Possible causes
- pushState is given a URL whose origin differs from the document
- A relative base or trailing slash resolves to a different origin
- The app is embedded and constructs a URL from the parent origin
Diagnosis steps
Compare the target with the document origin
console.log(location.origin); console.log(new URL(target, location.href).origin);Interpretation
Different origins explain the SecurityError; pushState only accepts same-origin URLs.
Reproduce the call
git grep -n "pushState\|replaceState"Interpretation
Auditing calls shows which one passes an absolute cross-origin URL.
Root cause
history.pushState and replaceState require the target URL to be same-origin with the document. Browsers raise SecurityError otherwise to prevent history manipulation across origins.
Fix
Pass only a same-origin path, query, and hash (for example "/users/1?tab=posts"), resolve relative URLs against location.href, and never push an external absolute URL.
Risk note
Manually rebuilding paths can drop the base path when the app is deployed under a subpath; derive the base from location.
Verify the fix
Navigate through the app and confirm the URL updates without error and back/forward work; assert the pushed URLs share location.origin.
Symptoms
When you hit “SecurityError — pushState Cannot Be Called on a Cross-Origin URL”, guessing wastes time. The guide below follows evidence: reproduce the symptom, run targeted diagnostics, identify the root cause, apply the fix, and verify it with concrete checks.
Observed symptoms include:
- The same-origin check failed at the pushState call
- pushState works locally but throws behind a CDN or on a different host
- The error names a URL with a different origin than the document
Reproduce and Diagnose
Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.
Step 1: Compare the target with the document origin
console.log(location.origin); console.log(new URL(target, location.href).origin);
Different origins explain the SecurityError; pushState only accepts same-origin URLs.
Step 2: Reproduce the call
git grep -n "pushState\|replaceState"
Auditing calls shows which one passes an absolute cross-origin URL.
Likely contributors, in order of frequency:
- pushState is given a URL whose origin differs from the document
- A relative base or trailing slash resolves to a different origin
- The app is embedded and constructs a URL from the parent origin
Root Cause
history.pushState and replaceState require the target URL to be same-origin with the document. Browsers raise SecurityError otherwise to prevent history manipulation across origins.
The Fix
Pass only a same-origin path, query, and hash (for example “/users/1?tab=posts”), resolve relative URLs against location.href, and never push an external absolute URL.
Watch out: Manually rebuilding paths can drop the base path when the app is deployed under a subpath; derive the base from location.
// ❌ cross-origin -> SecurityError
// history.pushState({}, '', 'https://evil.example.com/steal');
// ✅ same-origin path only
history.pushState({ page: 1 }, '', '/users/1?tab=posts');
// resolve relative to the current document origin
const next = new URL('/users/2', location.origin).pathname;
history.pushState({ page: 2 }, '', next);
Verify and Prevent
Navigate through the app and confirm the URL updates without error and back/forward work; assert the pushed URLs share location.origin.
Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.
This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.