shopify-vite icon indicating copy to clipboard operation
shopify-vite copied to clipboard

Trouble with CORS only in Shopify Theme Editor

Open rylanharper opened this issue 1 month ago • 25 comments

Hey Barrel team 👋

Thank you for this plugin, its been great so far!

For some reason I am having major CORS issues only in the Shopify theme editor. This started today (worked fine last night), so idk if its a Shopify issue or what. However, everything works perfectly fine in my local/network preview. I didn't commit any new css/scripts to my /src folder as well.

I have this vite.config file:

import { defineConfig, defaultAllowedOrigins } from 'vite'
import tailwindcss from '@tailwindcss/vite'
import shopify from 'vite-plugin-shopify'

export default defineConfig({
  plugins: [
    tailwindcss(),
    shopify({
      themeRoot: 'app',
      sourceCodeDir: 'src',
      entrypointsDir: 'src/entrypoints',
      snippetFile: 'vite-assets.liquid',
    }),
  ],
  server: {
    cors: {
      origin: [defaultAllowedOrigins, /\.myshopify\.com$/]
    }
  },
  build: {
    rollupOptions: {
      output: {
        entryFileNames: '[name].[hash].min.js',
        chunkFileNames: '[name].[hash].min.js',
        assetFileNames: '[name].[hash].min[extname]',
      },
    },
  },
})

In the Shopify Theme Editor I see this error, and I cannot use the editor in any way:

Image

rylanharper avatar Nov 05 '25 23:11 rylanharper

Hi @rylanharper (:

Probably Shopify has updated the Content Security Policy (CSP) on .myshopify.com domains to include stricter rules that may be blocking insecure (http://localhost) asset requests within the theme editor.

Serving assets through a secure HTTPS tunnel (e.g. ngrok) resolves the issue, allowing Vite’s assets to load as expected.

 // https://vitejs.dev/config/
 export default defineConfig({
-  plugins: [shopify(), tailwindcss()],
+  plugins: [
+    shopify({ tunnel: 'https://123abc.ngrok-free.app:5173' }),
+    tailwindcss()
+  ],
   server: {
+    allowedHosts: ['123abc.ngrok-free.app'],
     cors: {
       origin: [defaultAllowedOrigins, /\.myshopify\.com$/]
Image

montalvomiguelo avatar Nov 06 '25 03:11 montalvomiguelo

@montalvomiguelo Hmm okay got it. Its very odd everything changed all of a sudden, even though it was working for me the past few weeks locally and in a theme editor setting.

Alright, so I went through the ngrok download and setup and have everything working!

However, I have to be honest.. This feels horrible DX wise for me lol. Not with the plugin itself, but just the general workflow of connecting to Shopify. Do I have manually add in a new ngrok subdomain to my vite.config every time I develop my theme since the subdomains are auto-generated? What do you recommend here? Like whats the approach at Barrel if you don't mind me asking 🤔

I'm coming over from working with Shopify in a headless environment so all this is pretty new.

rylanharper avatar Nov 06 '25 04:11 rylanharper

@rylanharper I haven't found another solution yet; the issue started recently. Assets seem to load fine without the tunnel setup outside the theme editor

montalvomiguelo avatar Nov 06 '25 04:11 montalvomiguelo

The same issue. I've tried everything, but to no avail. This happened after updating the google chrome version to version 142.0.7444.135.

slo86 avatar Nov 06 '25 07:11 slo86

Same here, as @slo86 says, the issue is related to the latest chrome update and note related to Shopify Content Security Policy (CSP). Some of my teammates had the issue and I did not, until I updated to latest chrome version.

ChatGPT suggests it might be related to Private Network Access (PNA) enforcement in recent Chrome/Edge versions. When the storefront runs on HTTPS (e.g. https://website.com) and requests assets from http://localhost:5173, the browser performs a PNA preflight. If the dev server doesn’t return the header:

Access-Control-Allow-Private-Network: true

pablogiralt avatar Nov 06 '25 14:11 pablogiralt

How to fix it Type chrome://flags/#local-network-access-check into your Chrome address bar and press Enter. Find the "Local Network Access" flag and set its value to Disabled. Relaunch the browser when prompted

https://chromestatus.com/feature/5152728072060928

slo86 avatar Nov 06 '25 15:11 slo86

@slo86 genius! worked perfectly

pablogiralt avatar Nov 06 '25 15:11 pablogiralt

@slo86 Thanks for that info, this resolves the issue!:)

@montalvomiguelo Maybe this fix can be added to the docs under Troubleshooting section within the docs? Or should I just close out the issue?

rylanharper avatar Nov 06 '25 17:11 rylanharper

@rylanharper Could you please help me update the troubleshooting section of the documentation when you have a moment to close this issue? 🙏 🙏

montalvomiguelo avatar Nov 06 '25 20:11 montalvomiguelo

@montalvomiguelo Sure! I should have some time this Sunday/Monday to submit a small PR to edit the docs:)

rylanharper avatar Nov 07 '25 19:11 rylanharper

How to fix it Type chrome://flags/#local-network-access-check into your Chrome address bar and press Enter. Find the "Local Network Access" flag and set its value to Disabled. Relaunch the browser when prompted

https://chromestatus.com/feature/5152728072060928

This didn't work for me unfortunately

Image

lukecharle avatar Nov 09 '25 12:11 lukecharle

@lukecharle Fix your vite.config.js. Configure CORS for the dev server.

Image

Docs (troubleshooting) - https://shopify-vite.barrelny.com/guide/troubleshooting.html

slo86 avatar Nov 09 '25 13:11 slo86

@lukecharle Fix your vite.config.js. Configure CORS for the dev server.

Image

Docs (troubleshooting) - https://shopify-vite.barrelny.com/guide/troubleshooting.html

Yeah already have that in my config.

But after a bit of AI support, Cursor added the following under plugins and seems to be all working now! Hopefully this might be useful for others too.

{
  name: 'add-private-network-header',
  configureServer(server) {
    server.middlewares.use((req, res, next) => {
      res.setHeader('Access-Control-Allow-Private-Network', 'true');
      next();
    });
  }
}

lukecharle avatar Nov 09 '25 13:11 lukecharle

@montalvomiguelo I'm using a slightly more simple approach. If you just pass tunnel: true, then Shopify's CLI will automatically generate and use a Cloudflare tunnel.

shopify({
      themeRoot,
      snippetFile: assetsSnippetFile,
      versionNumbers: true,
      tunnel: true,
    }),

Then for the server I have the typical...

server: {
    cors: {
      origin: [
        /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/,
        /^https?:\/\/[^/]+\.myshopify\.com$/,
      ],
    },
  },

When you start up the dev server Shopify will handle all the tunnel magic.

Image

jonathanmoore avatar Nov 12 '25 15:11 jonathanmoore

Would love a vite.config solution (none of the above have worked for me), though the Chrome settings update did. Will follow for updates!

corykelley avatar Nov 12 '25 17:11 corykelley

@montalvomiguelo I'm using a slightly more simple approach. If you just pass tunnel: true, then Shopify's CLI will automatically generate and use a Cloudflare tunnel.

shopify({
      themeRoot,
      snippetFile: assetsSnippetFile,
      versionNumbers: true,
      tunnel: true,
    }),

Then for the server I have the typical...

server: {
    cors: {
      origin: [
        /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/,
        /^https?:\/\/[^/]+\.myshopify\.com$/,
      ],
    },
  },

When you start up the dev server Shopify will handle all the tunnel magic.

Image

Error

GET TUNNEL_URL net::ERR_ABORTED 403 (Forbidden)

✅ Fix

Adding allowedHosts: true in the Vite server config resolved the 403 error:

  server: {
+   allowedHosts: true,
    cors: {
      origin: [
        /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/,
        /^https?:\/\/[^/]+\.myshopify\.com$/,
      ],
    },
  },

⚠️ According to the Vite documentation, setting allowedHosts: true allows any host, which is not recommended for public tunnels.

I also tried a safer pattern-based configuration:

allowedHosts: [
  /\.ngrok-free\.app$/,
  /\.trycloudflare\.com$/,
  /\.myshopify\.com$/,
]

but it didn’t seem to work — "Could not start Cloudflare tunnel: max retries reached."


Full vite.config.mjs

import { defineConfig } from "vite";
import shopify from "vite-plugin-shopify";

export default defineConfig({
  plugins: [
    shopify({
      themeRoot: [ROOT_PATH],
      snippetFile: "react-snippet.liquid",
      versionNumbers: true,
      tunnel: true,
    }),
  ],
  server: {
    allowedHosts: true,
    cors: {
      origin: [
        /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/,
        /^https?:\/\/[^/]+\.myshopify\.com$/,
      ],
    },
  },
});

🧩 Dependencies

"vite": "^7.2.2"
"vite-plugin-shopify": "^4.0.2"

Everything builds and runs correctly with allowedHosts: true, but regex-based host filtering currently doesn’t work

INEEDAMONITOR avatar Nov 12 '25 18:11 INEEDAMONITOR

Lol I am unsure what to add to the docs now 😵‍💫

Maybe I add in the Google Chrome "Local Network Access" fix and the Cloudflare tunnel config from @jonathanmoore?

rylanharper avatar Nov 12 '25 18:11 rylanharper

I didn't have to modify anything with Chrome or add allowedHosts: true. Adding tunnel: true did the trick. https://shopify.dev/docs/apps/build/cli-for-apps/networking-options#cloudflare-quick-tunnels

@INEEDAMONITOR are you sure you are on the latest version of the Shopify CLI?

jonathanmoore avatar Nov 12 '25 18:11 jonathanmoore

Hmm, the tunnel approach and adding the store to allowed origins feels a little safer than turning off that update from Chrome?

montalvomiguelo avatar Nov 12 '25 21:11 montalvomiguelo

Hi,

If Shopify can add allow="local-network-access" attribute on <iframe> that would fix the issue. (Source: CORS issue with Version 142 but not in Version 141 - Chrome Enterprise & Education Community )

msev avatar Nov 13 '25 11:11 msev

@montalvomiguelo Agreed, the tunnel is safer than turning off Chrome's Local Network Access checks.. I reset my Chrome network settings and added @jonathanmoore's vite config, which is good, but needs the allowedHosts: true option like @INEEDAMONITOR mentioned (this is with the latest version of the CLI) for it to work fully.

I'm just wondering what a go-to config looks like for the documentation. If I can get a concrete example, I'll add a small PR to add this to the docs and close out this issue. What do you suggest @montalvomiguelo?

rylanharper avatar Nov 14 '25 19:11 rylanharper

@rylanharper It seems like the allowedHosts config is not needed when using cloudflared. I did not need to set it up on my end either. @jonathanmoore's config feels like the simplest recommended tunnel solution?

When using ngrok, Vite displays a message saying that allowedHosts needs to be configured; that's why I added it when I shared my solution with ngrok above.

When setting up allowedHosts, Vite recommends using an explicit list of allowed hosts https://vite.dev/config/server-options#server-allowedhosts. Same for cors https://vite.dev/config/server-options#server-cors

montalvomiguelo avatar Nov 14 '25 21:11 montalvomiguelo

~~Hmm maybe I'm just a noob, but is "cloudflared" the same as the Cloudlfare tunnel lol?~~ (I think that was a typo). I may be doing something wrong because the I get a CORS 403 error if the allowedHosts option is not set to true (although I see Vite says this is not recommended):

Failed to load resource: the server responded with a status of 403 ()

Yes, you are correct that its needed for ngrok, but I'm trying to avoid ngrok for better DX.

Edit: Maybe I should just post this topic on the Shopify dev forms and see what the Shopify team says? Seems like all this is due to recent Chrome updates, making a tunnel config necessary to bundle assets with vite. What @msev said is interesting and maybe I can bring that up in the post, but I dont know much about that iframe issue.

rylanharper avatar Nov 14 '25 21:11 rylanharper

I'll post this again here as it seems to have been missed..add the following under plugins and it 'should' work:

{
  name: 'add-private-network-header',
  configureServer(server) {
    server.middlewares.use((req, res, next) => {
      res.setHeader('Access-Control-Allow-Private-Network', 'true');
      next();
    });
  }
}

lspoor avatar Nov 15 '25 07:11 lspoor

hmm, that plugin approach does not seem to work on my end

montalvomiguelo avatar Nov 15 '25 21:11 montalvomiguelo

Hey all, here is the thread on the Shopify Developer Community Forms on this issue. The Shopify team is aware of the issue and will (hopefully) post an update on this sometime soon. In the meantime, I guess the tunnel or Chrome settings adjustment is the best way forward.

rylanharper avatar Nov 17 '25 19:11 rylanharper

I'll post this again here as it seems to have been missed..add the following under plugins and it 'should' work:

{
  name: 'add-private-network-header',
  configureServer(server) {
    server.middlewares.use((req, res, next) => {
      res.setHeader('Access-Control-Allow-Private-Network', 'true');
      next();
    });
  }
}

So this was working all fine for me, but now when I visit the preview url (stores domain), I'm getting:

...has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource

But it works fine in the customiser and on http://127.0.0.1:9292/

Honestly its just a new problem everyday 😂

lspoor avatar Nov 18 '25 16:11 lspoor