Shopify App 'Refused to Connect'? Navigating React Router v8 & App Bridge Issues
Hey folks! Ever been deep into developing your Shopify app, feeling good about your progress, and then BAM! You hit that dreaded "apps.shopify.com refused to connect" message? It's a frustrating wall to hit, especially when you're up against a Shopify compliance deadline. We recently saw a great discussion in the community about this exact issue, and I wanted to break down the insights shared by some of our sharpest developers.
Andrew, a store owner and developer, kicked off the thread with a classic scenario: his Shopify app, hosted on Fly.io, worked perfectly on the first load. But as soon as he navigated from the homepage to a customizer page and then back, he was greeted with a blank screen and that "refused to connect" error. This meant his app was breaking out of its iframe, causing an error loop. He was seeing /_root.data 202 in his logs, indicating a redirect was happening, but it was leading to the wrong place.
![]()
The Core Problem: React Router & Shopify App Bridge Mismatch
Lumine, a seasoned expert in the community, quickly zeroed in on the likely culprit: a version mismatch with react-router. Andrew was using react-router: 8.3.0, but the @shopify/shopify-app-react-router package (v2.0.0) declares a peer dependency on react-router: ^7.6.2. This means the Shopify adapter wasn't designed to work with React Router v8, which introduced significant changes to its single fetch pipeline.
In essence, the Shopify adapter relies on specific hooks in React Router v7 to catch reauthentication responses and handle them gracefully within the iframe using App Bridge. With v8, these hooks don't fire as expected. The .data request (which is what /_root.data 202 signifies) comes back unhandled, causing the browser to perform a top-level navigation. And when that navigation points to apps.shopify.com, it gets blocked by X-Frame-Options, leading to the dreaded "refused to connect" message.
Diving Deeper: The Redirect Target & Missing Authorization
MayraApps and Lumine further clarified that the _root.data 202 isn't an error in itself; it simply means your root loader threw a redirect. The critical detail was where that redirect was pointing. Andrew's response body showed it was redirecting to his public App Store listing (e.g., https://apps.shopify.com/zentra-announcement-bar), not an admin URL.
This is a key distinction. Shopify's libraries typically redirect to https://{shop}/admin/oauth/install for installation, not your public listing. If your app is redirecting to the public listing, it suggests a deeper issue: either the library can't resolve the shop context at all, or there's a hardcoded redirect in your own app's code.
Another crucial piece of the puzzle is the Authorization header. Lumine pointed out that if the _root.data request went out without this header, authenticate.admin would treat it as a document request. This often happens when App Bridge isn't properly initialized within the iframe. App Bridge is responsible for adding that header, so if window.shopify is undefined in your console, that's a huge red flag.
A common reason for App Bridge failing to load is an empty or missing data-api-key in the script tag. This usually stems from process.env.SHOPIFY_API_KEY not being set as a runtime secret in your hosting environment (like Fly.io), but rather only as a build argument. Without that API key, App Bridge can't initialize, and your app loses its crucial connection to the Shopify admin context.
Actionable Steps to Resolve the "Refused to Connect" Error
Based on the community's collective wisdom, here's a step-by-step guide to tackling this issue:
1. Pin Your React Router Version
The first and most critical step is to ensure your react-router version is compatible with the Shopify adapter. Andrew's problem was using v8 when v7 was expected.
- Check current versions: Run
npm ls react-routerto see what versions are installed and if there are any duplicates or hoisted versions. - Force v7.18.2: Add an
"overrides"block to yourpackage.jsonto forcereact-routerandreact-router-domto7.18.2. (If you're using Yarn, this is"resolutions"; for pnpm, it'spnpm.overrides)."overrides": { "react-router": "7.18.2", "react-router-dom": "7.18.2" } - Clean install: Delete your
node_modulesfolder and your lockfile (package-lock.jsonoryarn.lock), then runnpm install(oryarn install,pnpm install). - Verify again: Run
npm ls react-routerto confirm only7.18.2is present. Also, check yourviteplugin version;@react-router/dev7.18.2 is compatible with vite^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0, so versions like 7.3.6 are fine. - Rebuild and deploy:
npm run build && fly deploy(or your equivalent deploy command).
2. Verify App Bridge Initialization
If the version pinning doesn't solve it, or if you suspect App Bridge isn't working, these checks are crucial:
- Check
window.shopify: Open your browser's developer console while your app is loaded in the Shopify admin iframe. Typewindow.shopify. If it returnsundefined, App Bridge isn't loading. - Check
SHOPIFY_API_KEY: View your app's source code (within the iframe) and inspect the App Bridge script tag. It should look something like. Ensuredata-api-keyhas a value. If it's empty, yourSHOPIFY_API_KEYenvironment variable isn't being picked up at runtime. For Fly.io, Lumine suggested runningfly ssh console -C "printenv SHOPIFY_API_KEY"to confirm it's set as a runtime secret.
3. Identify and Correct Problematic Redirects
If your app is still redirecting to apps.shopify.com, you need to find where that URL is coming from and adjust your redirect logic.
- Inspect
_root.dataresponse: In your DevTools Network tab, click on the_root.datarequest and check its Response tab. The problematic URL will be sitting there in plain text. grepfor the URL: Use the command Lumine provided to find whereapps.shopify.comis referenced in your codebase:
If it's not ingrep -rn "apps.shopify.com" app/ server/ *.ts *.tsx *.js *.jsxapp/orserver/, widen your search tonode_modules, as a dependency might be carrying it.- Adjust redirect logic: If you find a custom redirect that sends users to your app listing page, you need to modify it. The Shopify adapter's
authenticate.adminhelper has an "escape hatch" for this. Instead of a plain redirect, you should pass{ target: '_top' }to ensure App Bridge handles the navigation correctly and breaks out of the iframe without error. This will typically result in a 401 with anX-Shopify-API-Request-Failure-Reauthorize-Urlheader, which App Bridge understands and uses to navigate the top frame.
Important caveat: Thisconst { redirect } = await authenticate.admin(request); throw redirect("https://apps.shopify.com/zentra-announcement-bar", { target: "_top" });target: '_top'approach relies on App Bridge patchingfetchand adding anAuthorizationheader. If App Bridge isn't alive (as per step 2), this 401 path won't fire, and you'll still get a 302. So, ensure App Bridge is working first!
This "refused to connect" issue is a classic example of how deeply integrated Shopify apps are with the platform's infrastructure. Keeping your dependencies aligned, ensuring App Bridge is initialized, and correctly handling redirects are paramount for a smooth user experience and staying compliant. Thanks to the detailed analysis from Lumine and MayraApps, Andrew (and hopefully you!) now have a clear path to getting that app back on track and passing those compliance deadlines.
