Polaris Frame has a weird transition flicker when using the Navigation Menu in my embedded app

Hi everyone,

I’m developing an embedded Shopify app using Next.js and @shopify/app-bridge-react v3. I’ve noticed a visual “flicker” or transition animation whenever I click on links in the Navigation Menu. Inspecting the DOM shows that the parent container (within the Shopify Admin) — specifically a div with classes like Polaris-Frame_Scrollable — is being re-rendered or updated with transition CSS classes. This creates a noticeable “page flicker” or brief animation on navigation.

if I don’t click the navigation menu links and instead programmatically dispatch a redirect action, the outer Polaris frame container does not change and there’s no visible transition. That suggests the Navigation Menu might be doing extra work beyond a simple redirect.

Apparently, this is the behavior of Shopify Admin frontend and I have no control over it. BUT I’ve tested some other embedded apps, and they don’t exhibit this transition, I wonder how do they workaround with it?

I’ve taken several screenshots for the DOM update, hoping it would provide necessary context.

This is the nav menu link transition on my app, you can see there is an extra _loading_xxx css class added, causing the page flickers. At the end of the transition, this div gets updated as well.



However, this is not the case in some other apps, the div simply adds some inline styles and then gets removed when transition ends.

Hi Haozhongzheng

We’re looking into this as part of this issue Issue with Navigation Menu Reloading Before Redirecting - #3 by haozhongzheng

There is a GitHub issue for this issue already.
You can find some solutions.

Thanks for your reply @remy727 . However we’re using app bridge v3 right now, migrating to v4 may refactor a lot of things. I hope shopify dev staff could locate the problem first so we can decide if v4 is really necessary.

Hi @haozhongzheng - It does look like this is a regression of this issue: App Bridge 4 clicking on Navigation Menu causes app reload · Issue #240 · Shopify/shopify-app-bridge · GitHub

Our team are still investigating, will update as soon as I hear more.

I have the same exact problem with App Bridge 4
In my case I have a rails + hotwire app. Since I don’t want the links to make a full-page reload, this is how I handle the navigation from the ui-nav-menu component:

// <ui-nav-menu data-controller="navigation">

export default class extends Controller {
  connect() {
    const links = this.element.querySelectorAll("a");

    links.forEach((link) => {
      link.addEventListener("click", this.#handleClick);
    });
  }

  #handleClick(event) {
    event.preventDefault();
    Turbo.visit(event.currentTarget.href);
  }
}

Also, on firefox you cannot see this transition since view-transition-name is not supported there

Hi @Liam-Shopify

Any news here?

Hi folks,

Our team are still working on this issue - no update to share yet.

FYI after weekends i’m not facing the problem, idk what they did but now it works for me

I believe this issue is currently happening in our app as well.

We have a strictly back-end app. Sometimes, when navigating to a different page using the sidebar menu, the current page will fade to gray, and then back to fully visible again before the destination page is finally shown. It creates a “flicker” effect.

It doesn’t always happen, and might be related to page load speed. But it happens with page loads less than 500ms. So, not sure if there’s a cutoff.

The general “fade to gray” is fine. The issue is that before the next page is shown, the current page pops back in, which is unexpected.

Here is a video that shows the issue in slow-motion. In the video, I start on the “Code Reference” page and click on the home link to visit the “Workflows” page. You can see that the “Code Reference” page fades out, then pops back in, then finally the “Workflows” page loads.

@danatbonify Exact same behavior here. Unfortunately after a few months it’s still not been solved.

@Liam-Shopify do you have any further updates on this? I am seeing the same thing as described by the video from @danatbonify - and as you can imagine, it is pretty terrible :slight_smile: Just like I him, I am using a mostly back-end rendered setup, but in NextJS App router.

Not only I have a flicker, but clicking the link with “rel=”home”” will not do anything except change the apploadId in the top window. It seems app bridge do some magic that completely removes the click event listener from first link.

ui-nav-menu has some really weird behaviours, I’ve only managed to make the home link work using some work arounds, it seems that if you click the rel=”home” it will add a appLoadId parameter and it expects you to reload the entire app.

This is not the full code but it might help someone who is not using react, this is my svelte code (this is just a callback to the “message” event):

```

<svelte:window
  onmessage={(
    /**
     * Workaround to force shopify to navigate correcly to home
     * @type{MessageEvent<{ payload?: { payload?: { path: string; }; group: string; type: string; }; type?: string; }>}
     */
    e,
  ) => {
    console.log("MESSAGE data:", e.data);
    if (
      e.data.type == "dispatch" &&
      e.data.payload?.group == "Navigation" &&
      e.data.payload.type == "APP::NAVIGATION::REDIRECT::APP" &&
      e.data.payload.payload?.path
    ) {
      console.log("RECEIVED REDIRECT EVENT", e.data);
      const path = e.data.payload.payload.path;
      if (path.startsWith(`/apps/${appName}?appLoadId=`)) {
        if (currPath != "/" && currPath != "") {
          console.log("manually redirecting to home");
          currPath = "";
        }
      }
    }
    /** @example event
     * ```json
     * {
     *   "payload": {
     *     "path": "/apps/checkout_discount?appLoadId=3e8e6dd9-d54e-4d48-b5c5-44db73f2cb06"
     *   },
     *   "group": "Navigation",
     *   "type": "APP::NAVIGATION::REDIRECT::APP"
     * }
     * ```
     */
  }} />

Hi everyone,

I’m developing a Shopify app using Remix and I’ve noticed a flicker issue with the Polaris Card component. On page reload, the card briefly renders without its border radius, then almost immediately re-renders with the correct styling.
below are the image that render the card flicker issue with there border radius.
I tried using defer on the server side, but it didn’t help.

hey have you found any solution yet

I have the same issue… Any news ? thx

Hey everyone,

We’ve been fighting the issue for weeks:
In our embedded Shopify app (React + App Bridge), clicking on sidebar links (or any navigation inside the admin) would often cause a white screen, slow load, UI flicker, or even a full iframe reload instead of a smooth SPA transition.

After deep debugging, we finally found the root cause – and it might help you too.

Root cause (the real problem)

Multiple App Bridge instances competing inside the same iframe.

We made two classic mistakes:

  1. Using both the CDN script and the @shopify/app-bridge npm package
    In index.html we had:
    <script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>
    And in our components we also used createApp() from the npm package (@shopify/app-bridge v3.7.11).
    → Two different App Bridge versions trying to control the same iframe.

  2. Calling createApp() inside React components
    In useAuthenticatedFetch.js and many other places, we did:

    const app = createApp({ apiKey, host });
    

    Every render created a new App Bridge instance. Because the hook was used in dozens of components, each navigation registered 10+ instances. Shopify Admin detected multiple instances and force‑reloaded the iframe as a safety measure.

  3. Recreating QueryClient on every render (minor but added chaos)
    We had new QueryClient() inside QueryProvider without useState, causing the whole React tree to unmount/remount.

The fix (what finally worked)

1. Stop using createApp() – rely on the CDN instance

Instead of creating your own App Bridge, use the one provided by Shopify CDN via @shopify/app-bridge-react:

// useAuthenticatedFetch.js – fixed version
import { useAppBridge } from '@shopify/app-bridge-react';
import { useCallback } from 'react';

export function useAuthenticatedFetch() {
  const shopify = useAppBridge(); // ← CDN instance, no createApp()

  return useCallback(async (uri, options) => {
    const token = await shopify.idToken(); // token is cached internally
    const response = await window.fetch(uri, {
      ...options,
      headers: {
        ...options?.headers,
        Authorization: `Bearer ${token}`,
      },
    });

    if (response.headers.get('X-Shopify-API-Request-Failure-Reauthorize') === '1') {
      const authUrl = response.headers.get('X-Shopify-API-Request-Failure-Reauthorize-Url') || '/api/auth';
      const redirectUrl = authUrl.startsWith('/')
        ? `https://${window.location.host}${authUrl}`
        : authUrl;
      window.open(redirectUrl);
    }

    return response;
  }, [shopify]);
}

2. Use native Shopify navigation instead of Redirect.create()

We replaced all Redirect.create(app).dispatch(...) with window.open('shopify://admin/...', '_top') – this is the official navigation scheme for Shopify Admin.

// ❌ Old way (creates new App Bridge instance)
const app = createApp({ apiKey, host });
const redirect = Redirect.create(app);
redirect.dispatch(Redirect.Action.ADMIN_SECTION, {
  name: Redirect.ResourceType.Product,
  resource: { id: product.id }
});

// ✅ New way (no extra instance, works reliably)
window.open(`shopify://admin/products/${product.id}`, '_top');

3. Stabilize QueryClient in your provider

// ❌ Before – new client on every render
const client = new QueryClient({ ... });

// ✅ After – created once
const [client] = useState(() => new QueryClient({ ... }));

4. Remove unnecessary packages

After the changes, we uninstalled @shopify/app-bridge and @shopify/app-bridge-utils – they are no longer needed.

Summary

Never call createApp() inside a React component.
Every call registers a new App Bridge instance. Multiple instances confuse Shopify Admin and lead to forced full‑reload navigations.

  • Need a token? Use shopify.idToken() from useAppBridge() (CDN instance).

  • Need navigation inside admin? Use window.open('shopify://admin/...', '_top').

  • Keep a single App Bridge instance (the one loaded from CDN).