Shopify app - Failed to load module script: Expected JS module but server responded with "text/jsx"

I’m trying to create a Shopify app without using the CLI. I’m using Express for the server, I created the react app with npm create [email removed] and ngrok is running to tunnel the app to my local environment.

I have written the server index.js file much like Shopify have in their latest app template. I haven’t touched the vite config file and it is not the same as theirs as I don’t fully understand what they’re doing with it yet (my first time using vite).

The issue I’m having is when loading the JS/JSX file in the HTML document. I get the following error:

Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of “text/jsx”. Strict MIME type checking is enforced for module scripts per HTML spec.

I can successfully output the contents of a normal HTML file into the embedded app within Shopify’s admin by changing the file the route is returning. I can also output elements such as headings when I insert them in the index.html file. So it’s retrieving that fine.

If I change the type of the script tag in index.html to “text/javascript”, I then obviously I get the error Uncaught SyntaxError: Cannot use import statement outside a module (at main.jsx:1:1).

I also tried the typical npx create-react-app. This also doesn’t work when accessed through the Shopify app, although it doesn’t return the error in the console. It’s just blank.

The app works fine when I access it directly through localhost:PORT.

This is the server’s index.js:

import dotenv from 'dotenv';
import { readFileSync } from "fs";
import express from "express";
import serveStatic from "serve-static";
import { shopifyApp } from '@shopify/shopify-app-express';
import GDPRWebhookHandlers from "./gdpr.js";

dotenv.config();

const PORT = process.env.PORT ? process.env.PORT : 3500;

const shopify = shopifyApp({
  api: {
    apiKey: process.env.API_KEY,
    apiSecretKey: process.env.API_SECRET_KEY,
    scopes: process.env.SCOPES,
    hostName: process.env.HOST_NAME,
  },
  auth: {
    path: '/api/auth',
    callbackPath: '/api/auth/callback',
  },
  webhooks: {
    path: '/api/webhooks'
  },
});

const app = express();

app.get(shopify.config.auth.path, shopify.auth.begin());
app.get(
  shopify.config.auth.callbackPath,
  shopify.auth.callback(),
  shopify.redirectToShopifyOrAppRoot(),
);
app.post(
  shopify.config.webhooks.path,
  shopify.processWebhooks({ webhookHandlers: GDPRWebhookHandlers })
)

// All endpoints after this point will require an active session
app.use("/api/*", shopify.validateAuthenticatedSession());

app.use(express.json());

app.use(serveStatic(`${process.cwd()}/client/`, { index: false }));

app.use("/*", shopify.ensureInstalledOnShop(), async (_req, res, _next) => {
  return res
    .status(200)
    .set("Content-Type", "text/html")
    .send(readFileSync(`${process.cwd()}/client/index.html`));
});

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

The vite.config.js file:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
})

The index.html file:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

The main.jsx file:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)
3 Likes

Had similar problem, did you by any chance figure this out?

Greatly appreciate your guidance in advance.

1 Like

I am going through the same exact problem, except that I use webpack instead of vite. My index.html file imports the bundled react through “

If I run the index.html in localhost it works fine. It seems that

shopify.ensureInstalledOnShop() blocks the request when index.html tries to import the script files. I don’t know how to properly server the files. I tried to look through the CLI source code to see how they do it, but I haven’t found out how.
I hope you have found a solution?

1 Like

I eventually got it working but the hot reload wasn’t working. I ended up scrapping it and just using the boilerplate that shopify provide through the CLI. I’d love to tell you how I got it going but it was way too long ago and I have no idea anymore lol

1 Like

I went through this same problem and can’t believe wasted couple of days fighting it. In my case the express server wasn’t serving the index file from the dist folder and was serving some jsx files instead.


const STATIC_PATH = process.env.NODE_ENV === 'production'
? `${process.cwd()}/frontend/dist/`
: `${process.cwd()}/frontend/`;

I figured my NODE_ENV wasn’t set to ‘production’ in the web folder due to which the ‘jsx’ files was getting served ?. Setting it to ‘production’ finally fixed it for me.

1 Like