vercel/next.js · error · Error
Next.js has blocked a javascript: URL as a security precauti
Error message
Next.js has blocked a javascript: URL as a security precaution.
What it means
The experimental `gesturePush` method on the App Router throws when the href is a `javascript:` URL. Next.js blocks javascript: URLs in all navigation methods as a security precaution against XSS — a javascript: href could execute arbitrary code. This guard protects gesture-based navigation (enabled via experimental.gestureTransition).
Source
Thrown at packages/next/src/client/components/app-router-instance.ts:340
url: new URL(href),
historyState,
})
}
/**
* (Experimental) Perform a gesture navigation. This dispatches through React's
* useOptimistic instead of the main action queue, allowing the state to be
* shown during a gesture transition and discarded when the canonical navigation
* completes.
*
* Only available when experimental.gestureTransition is enabled.
*/
function gesturePush(href: string, options?: NavigateOptions): void {
if (process.env.__NEXT_GESTURE_TRANSITION) {
// TODO: Trigger a prefetch so the cache starts populating if there isn't
// already a prefetch for this route.
if (isJavaScriptURLString(href)) {
throw new Error(
'Next.js has blocked a javascript: URL as a security precaution.'
)
}
const state = getCurrentAppRouterState()
if (state === null) {
return
}
const url = new URL(addBasePath(href), location.href)
if (isExternalURL(url)) {
return
}
// Fork the router state for the duration of the gesture transition.
const currentUrl = new URL(state.canonicalUrl, location.href)
const scrollBehavior =
options?.scroll === false
? ScrollBehavior.NoScrollView on GitHub (pinned to 0ae8c72462)
Solutions
- Replace the javascript: URL with a valid route path or '#'.
- Sanitize and validate all dynamic href values before passing to router navigation methods.
- If you need a no-op link, use `href='#'` with an onClick handler that calls preventDefault.
- Strip or reject any href matching /^javascript:/i at the data source.
Example fix
// before — blocked
router.gesturePush('javascript:alert(1)')
// after — use a real route or a safe placeholder
router.gesturePush('/dashboard')
// or for a no-op button:
<button onClick={(e) => { e.preventDefault(); doSomething() }}>Click</button> Defensive patterns
Strategy: validation
Validate before calling
function isJavaScriptURL(href: string): boolean {
return /^\s*javascript:/i.test(href)
}
function safeHref(href: string): string {
return isJavaScriptURL(href) ? '/' : href
} Type guard
function isSafeNavigationHref(href: unknown): href is string {
return typeof href === 'string' && !/^\s*javascript:/i.test(href)
} Prevention
- Sanitize all dynamic href values at the trust boundary (API parsing, form input).
- Never use javascript: URLs in navigation — use route paths or onClick handlers.
- Add a lint rule or runtime guard to reject javascript: hrefs in navigation calls.
When it happens
Trigger: Passing a string starting with `javascript:` to the gesture navigation API (gesturePush), which is triggered when experimental.gestureTransition is enabled.
Common situations: Dynamic href values from untrusted or user-generated content (CMS, API data); legacy `javascript:void(0)` placeholder links; bookmarklet-style URLs; a data binding bug that injects a javascript: scheme.
Related errors
- Method unavailable on `ReadonlyURLSearchParams`. Read more:
- hmrRefresh can only be used in development mode. Please use
- Cannot prefetch '${href}' because it cannot be converted to
- Invalid Server Action payload: failed to decrypt.
- Taint can only be used with the taint flag.
AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06).
Data as JSON: /api/errors/3a60645b7b3e1dec.
Report an issue: GitHub.