Skip to main content

admin_ui blank dashboard after Vite 8 upgrade (2026-06-19)

Summary

  • All merchants' Shopify Marqo dashboards rendered blank in the embedded iframe after the 2026-06-19 01:34 UTC prod deploy.
  • The dashboard's chrome (left nav, "Marqo" breadcrumb title, the iframe element itself) appeared, but the iframe's content area was empty.
  • Root cause: PR #3651 (Dependabot, merged 2026-06-18T12:46 UTC) bumped Vite from ^5.4.8 to ^8.0.16. Vite 7 switched its internal globber from micromatch to tinyglobby, which silently does not match extglob negation patterns like !(*.test.[jt]sx). The import.meta.glob call in components/shopify/admin_ui/src/App.tsx used such a pattern; on Vite 8 it returned {}, leaving the React Router routes table empty.
  • Fix: rewrite the glob to match all .tsx/.jsx files in src/pages/ and filter out *.test.tsx/*.test.jsx in JS instead of relying on extglob. Works on both Vite 5 and Vite 8.

Timeline (UTC)

  • 2026-06-18T12:46 — PR #3651 merged to main (Dependabot bump: vite ^5.4.8^8.0.16, plus vitest, @vitejs/plugin-react, @vitest/coverage-v8, esbuild). CI checks all green (build compiled, unit tests passed). No e2e tests run on this PR. e2e-tests and e2e-ui-tests reported as SKIPPED.
  • 2026-06-18T15:20Deploy Ecommerce Platform Pipeline for sha=1873b818 (which contains the dep bump) starts on main. Staged rollout (dev → staging → preprod → prod) with approval gates.
  • 2026-06-19T01:34 — Prod-stage BucketDeployment uploads the new index.html, index-CmKfZYbo.js (393.7 KB), and index-C77IbKlF.css to s3://prod-shopify-app-assets/. Previous bundle was index-CbQVTN_t.js (576.8 KB from 2026-06-11). The size dropped 31 % because all src/pages/** modules dead-code-eliminated themselves out of the entry bundle (no longer reached via import.meta.glob).
  • 2026-06-19T01:51 — Deploy pipeline reports success.
  • 2026-06-19 morning — Merchants report blank Marqo dashboards on https://admin.shopify.com/store/<shop>/apps/marqo-<shop>.

What was verified

App Bridge IS working

In a synthetic parent test (a <iframe src="https://admin.ecom.marqo.ai/?embedded=1&shop=msqc.myshopify.com&host=..."> hosted on localhost:8765) we captured 18 outbound postMessage calls from the iframe, including:

  • APP::CLIENT::INITIALIZE (dispatch + subscribe)
  • APP::CLIENT::RPC subscribe
  • APP::NAVIGATION::HISTORY::REPLACE (router pushes /)
  • APP::TITLEBAR::BUTTONS::BUTTON::CLICK subscribe
  • APP::TITLEBAR::BREADCRUMBS::BUTTON::CLICK subscribe
  • APP::MARKETING_EXTERNAL_ACTIVITY_TOP_BAR::BUTTONS::BUTTON::CLICK subscribe
  • APP::LOADING::STOP — the app explicitly tells Shopify "I'm done loading"

Same set of messages observed when reloading the iframe inside the real Shopify admin shell at admin.shopify.com. So:

  • The bundle executes.
  • React mounts.
  • @shopify/app-bridge-react initializes.
  • The NavMenu <a> declarations reach Shopify (breadcrumb "Marqo" renders in the outer chrome).
  • The app signals load-complete.

Where the bundle silently failed

src/App.tsx:15 (before the fix):

const pages = import.meta.glob<PageModule>(
"./pages/**/!(*.test.[jt]sx)*.([jt]sx)",
{ eager: true },
);

Routes.tsx then turned pages into a React Router route table:

const routes = Object.keys(pages).map(...);

On Vite 8, pages === {} because tinyglobby doesn't honor the extglob negation. So routes === [], and <ReactRouterRoutes> mounted with zero children. React Router rendered nothing for /, hence the blank content area. The NavMenu / breadcrumbs / App Bridge handshake still worked because they're declared inline in App.tsx and don't depend on the pages glob.

Bundle string-presence check confirmed the page modules dropped out of the build entirely:

StringBroken prod (Vite 8)June 11 working (Vite 5)After fix (Vite 8)
NotFound022
ExitIframe011

Bundle sizes:

Bytes (gz)Notes
2026-06-11 working576,764Vite 5, micromatch — extglob worked
2026-06-19 broken393,700 (-31 %)Vite 8, tinyglobby — pages dead-stripped
After fix563,512 (+43 %)Vite 8, simple glob + JS filter — pages restored

Why no console error, no test failure

  • React Router rendering an empty <Routes> is valid — not a crash.
  • The bundle's npm run build step succeeded (TypeScript compiled, Vite emitted output).
  • npm test covers src/* unit tests; none of them exercise App.tsx's page-glob result.
  • No e2e test loads the embedded iframe and asserts that any page route mounts.
  • Dependabot CI for this PR ran only the green checks above. e2e-tests and e2e-ui-tests reported SKIPPED.

That's the gap: a passing vite build plus passing unit tests do not detect "the entry bundle no longer contains any of the page modules."

Mechanism of the postMessage console warning

While the iframe is broken-blank, console occasionally shows:

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://admin.ecom.marqo.ai') does not match the recipient window's origin ('https://admin.shopify.com').

This is from Shopify's outer admin code (common-42985c2a6792.js) targeting admin.ecom.marqo.ai but landing on a window that's at admin.shopify.com (transiently — typically while the iframe is between about:blank and the new document during reload). It's noise that pre-dates this incident and is not what's making the dashboard blank. Initial triage anchored on this error and the bundle's 31 % size drop and reached for the symptom rather than the route table. The synthetic-parent test that captured App Bridge messages was the key signal that flipped the diagnosis — the bundle WAS working, just rendering nothing.

Root cause

Vite 7 switched its default internal glob library from micromatch to tinyglobby for import.meta.glob. tinyglobby does not implement extglob negation (!(...)), so the pattern ./pages/**/!(*.test.[jt]sx)*.([jt]sx) silently matches zero files on Vite 7 and later. The Dependabot PR #3651 upgraded Vite from 5 to 8 in one step; the existing glob pattern in App.tsx was not updated to a tinyglobby-compatible form.

Fix

Extracted the pages glob into components/shopify/admin_ui/src/loadPages.ts so the test below can exercise the same function App.tsx calls (instead of duplicating the pattern):

// src/loadPages.ts
export function loadPages(): Record<string, PageModule> {
const allPages = import.meta.glob<PageModule>("./pages/**/*.{tsx,jsx}", {
eager: true,
});
return Object.fromEntries(
Object.entries(allPages).filter(([key]) => !/\.test\.[jt]sx$/.test(key)),
);
}
// src/App.tsx (now)
import { loadPages } from "./loadPages";
// ...
const pages = loadPages();

This works on both Vite 5 (micromatch) and Vite 8 (tinyglobby). Verified locally that the rebuilt bundle restores the page modules (NotFound, ExitIframe strings present in the entry bundle) and matches the previous working bundle's size class (~563 KB vs broken 393 KB vs June 11 working 576 KB).

Regression guard

components/shopify/admin_ui/tests/loadPages.test.ts asserts that loadPages():

  • returns a non-empty record (catches silent zero-match globber regressions)
  • includes the known core pages (index.tsx, settings.tsx, NotFound.tsx, ExitIframe.tsx)
  • yields a function-valued default export for every entry
  • still excludes *.test.[jt]sx files

The first two assertions fail loudly if the glob ever silently matches nothing again. Manually verified: temporarily restoring the broken extglob pattern in loadPages.ts makes 2/4 of those cases fail; restoring the fix brings the full suite back to 50/50 green.

This is a unit-level guard; it does not require running a browser or rebuilding the bundle. The "Followups" below cover deeper layers.

Followups

  1. Add a build-output assertion to admin_ui CI. After npm run build, run a headless browser that loads dist/index.html and asserts a known page route renders. ≤ 2 minutes of CI time, catches this class of silent regression at the post-bundle stage in addition to the unit-level guard. The current npm run build step verifies the bundler exited 0; that's not the same as the bundle being functional.
  2. Add Dependabot major-version handling for admin_ui. Currently major bumps merge with the same CI as patches. Consider either grouping multi-major bumps for human review, or pinning major versions and ignoring them in .github/dependabot.yml.
  3. Add an e2e test that loads the embedded admin iframe. The existing components/shopify/e2e_tests/ Playwright suite covers storefront, install/uninstall, and admin worker — none of them load admin.ecom.marqo.ai/?embedded=1&shop=... and assert that a route renders. A single spec would block this exact regression class going forward.
  4. Audit import.meta.glob usages across the repo for other extglob negation patterns that may break on Vite 7+. Same risk wherever extglob !(...) appears.