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.NoScroll

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Replace the javascript: URL with a valid route path or '#'.
  2. Sanitize and validate all dynamic href values before passing to router navigation methods.
  3. If you need a no-op link, use `href='#'` with an onClick handler that calls preventDefault.
  4. 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

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


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/3a60645b7b3e1dec. Report an issue: GitHub.