App Bridge: one unparseable <a href> anywhere in the app document silently breaks admin sidebar navigation

Summary
When a merchant clicks an app nav item in the admin sidebar (<ui-nav-menu> / NavMenu), App Bridge looks for the matching anchor by calling new URL(href) on every a, s-link, s-button and s-clickable in the app document, not only the nav menu’s children. If any of them has an href that new URL() rejects, the call throws. The navigation is then dropped: the admin URL changes, but the app never navigates. Nothing visible tells the merchant or the developer why.

Environment

  • https://cdn.shopify.com/shopifycloud/app-bridge.js (unversioned CDN build, fetched 2026-10-22)
  • Embedded app, React Router BrowserRouter, nav declared with NavMenu from @shopify/app-bridge-react (<Link to="/sections"> children)
  • Chrome, Shopify admin web

Steps to reproduce

  1. In an embedded app with a NavMenu, render anywhere in the app document (outside the nav menu):

    <a href="http://查看:https://example.com/">link</a>
    

    The host contains a full-width colon (U+FF1A). Chat widgets produce exactly this when they auto-link a URL glued to CJK text.

  2. In the admin sidebar, click a nav item other than the current page.

Expected
The app navigates to the clicked route. An unrelated invalid anchor should be skipped.

Actual

  • The admin URL changes (e.g. /apps/<app>/sections), but the app stays on the previous screen.

  • The console shows:

    Uncaught (in promise) TypeError: Failed to construct 'URL': Invalid URL
        at on (app-bridge.js:1)
        at (anonymous) (app-bridge.js:1)
        at an (app-bridge.js:1)
        at r (app-bridge.js:1)
        at dispatchEvent (app-bridge.js:1)
    

Analysis (names are from the minified build, so they may change)

  • rn() builds a selector that, besides ui-nav-menu > a and s-app-nav > a, also includes plain a, s-link, s-button and s-clickable, then calls document.querySelectorAll on it. That returns every anchor in the document.
  • an() runs rn().filter(el => ln(target, on(el.getAttribute('href')))).
  • on() calls new URL(href, location.href). For shopify:, app: and extension: URLs it also re-parses new URL(protocol + '/' + ...). Neither call is wrapped in a try/catch, so one bad href rejects the navigate handler.

Suggested fix

  • Skip anchors whose href fails to parse, for example with a try/catch inside the filter callback (or around on()).
  • And/or scope the lookup to the nav menu’s own children (ui-nav-menu > a, s-app-nav > a) before falling back to the whole document.

Impact

  • Real merchant reports: sidebar navigation “does nothing” in our app, only for stores whose support chat history contains such a message.
  • Content the app does not control (third-party widgets, merchant-entered text) can break core admin navigation.
  • We have shipped an app-side workaround that repairs invalid hrefs inside our chat widget. The general case can only be fixed in App Bridge.

Hi @Harry_Pagefly thanks for the detailed report.

I confirmed in an isolated test of the App Bridge code that a malformed link elsewhere in the document can interrupt navigation. I’ve shared the findings for engineering review, including the earlier report of this behavior and the workaround you’ve already shipped in your widget.

I’ll follow up here when there’s an update to share.