New Shopify Certification now available: Liquid Storefronts for Theme Developers

CORS issue when requesting data from shopify app (remix template) during local theme development

Solved
Danh11
Shopify Partner
62 2 20

I am developing ontop of the Remix app template. I have a route setup to return user data from the database.

 

I am making a request via the shopify app proxy to a route setup to fetch data from the app.

I understand the shopify app proxy won't work while running the theme locally (I think?), so I have a domain pointing to a tunnel which points to the app running on localhost.

 

I can successfully make requests by hitting the endpoint via the tunnel in my browser.

 

Although the request is rejected when being made from the theme locally. I am returning headers to the preflight request, which I think are right, but it's still not working.

 

I'm wondering if some middlewear when running the theme locally is interfering with something.

 

api.get-user.jsx

export async function loader({ request }) {
  const corsHeaders = {
    "Access-Control-Allow-Origin": "http://127.0.0.1:9292",
    "Access-Control-Allow-Methods": "GET, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type",
  };

  if (request.method === 'OPTIONS') {
    return json({
      status: 200,
      headers: corsHeaders,
      body: ''
    });
  }
  
  return json({ body: 'data' });
}

JS in theme

async getUser() {    
    const url = `${this.appUrl}/api/get-user`;
  
    try {
      const response = await fetch(url, {
        method: 'GET',
        headers: { 'Content-Type': 'application/json' },
      });
  
      if (!response.ok) throw new Error('Network response error:', response.statusText);
  
      const user = await response.json();
  
      return user;
  
    } catch (error) {
      console.error('Error:', error);
    }
  }


I am aware that I should be checking the digital signature from the request, and the preflight response headers probably aren't the greatest, but I'm just trying to get this working first.

Accepted Solution (1)
Danh11
Shopify Partner
62 2 20

This is an accepted solution.

The way to get this working is to use the CORS function from the remix-utils package.

 

If installing this on the Remix template of the shopify app, you will need to modify the remix.config.js file by including serverDependenciesToBundle: [ /^remix-utils.*/ ] in the module.exports as documented here.  This is because remix-utils is published as ESM only and the remix.config.js file has the serverModuleFormat set to cjs. My editor still yells at me about the incorrect import method, but it works nonetheless. This is my updated remix.config.js file:

 

remix.config.js

 

if (
  process.env.HOST &&
  (!process.env.SHOPIFY_APP_URL ||
    process.env.SHOPIFY_APP_URL === process.env.HOST)
) {
  process.env.SHOPIFY_APP_URL = process.env.HOST;
  delete process.env.HOST;
}

/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
  ignoredRouteFiles: ["**/.*"],
  appDirectory: "app",
  serverModuleFormat: "cjs",
  future: {
    v2_errorBoundary: true,
    v2_headers: true,
    v2_meta: true,
    v2_normalizeFormMethod: true,
    v2_routeConvention: true,
    v2_dev: {
      port: process.env.HMR_SERVER_PORT || 8002,
    },
  },
  serverDependenciesToBundle: [
    /^remix-utils.*/,
  ],
};

 

Then, import cors from the remix-utils package and update the return in your loader function of your API route with the cors function, as per their docs linked above. It should look like the following:

 

api.get-user.jsx

 

import { json } from '@remix-run/node';
import { cors } from 'remix-utils/cors';

export async function loader({ request }) {

  const response = json({ body: 'data' });

  return await cors(request, response);
}

 

I am now successfully making requests from the theme which is running locally.

 

View solution in original post

Replies 6 (6)
Liam
Shopify Staff
Shopify Staff
1898 202 577

Hi Danh11,

 

From your code, it seems like you're handling the CORSflight request. However, you might want to add the "Access-Control-Allow-Credentials" header and set it to "true" if you're using cookies for authentication. Also, consider allowing all headers in theAccess-Control-Allow-Headers" in your CORS configuration for testing purposes:

const cors = {
  "Access-Control-Allow-Origin": "http://1270.0.1:9292",
  "Access-Control-Allow-Methods": "GET, OPTIONS",
  "Access-Control-Headers": "*",
  "Access-Control-Allow-Credentials": "true"
};

Moreover, ensure that your server is set up to handle OPTIONS requests. The preflight request is an OPTIONS request and server needs to respond with the appropriate CORS headers.

 

Hope this helps!

Liam | Developer Advocate @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit Shopify.dev or the Shopify Web Design and Development Blog

Danh11
Shopify Partner
62 2 20

Unfortunately the request is still being blocked. There were a couple of typos which I think were a mistake? By `Access-Control-Headers` I assume you meant `

Access-Control-Allow-Headers`? Fixing them didn't resolve the issue though.
 
As I'm using the Shopify app template, a lot of the server config is hidden and I can't find a way to access or modify it. All that I can see is `shopify.server.js`. I'm not able to find any info on how to modify the express instance which I think is running.
 
 
Danh11
Shopify Partner
62 2 20

This is an accepted solution.

The way to get this working is to use the CORS function from the remix-utils package.

 

If installing this on the Remix template of the shopify app, you will need to modify the remix.config.js file by including serverDependenciesToBundle: [ /^remix-utils.*/ ] in the module.exports as documented here.  This is because remix-utils is published as ESM only and the remix.config.js file has the serverModuleFormat set to cjs. My editor still yells at me about the incorrect import method, but it works nonetheless. This is my updated remix.config.js file:

 

remix.config.js

 

if (
  process.env.HOST &&
  (!process.env.SHOPIFY_APP_URL ||
    process.env.SHOPIFY_APP_URL === process.env.HOST)
) {
  process.env.SHOPIFY_APP_URL = process.env.HOST;
  delete process.env.HOST;
}

/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
  ignoredRouteFiles: ["**/.*"],
  appDirectory: "app",
  serverModuleFormat: "cjs",
  future: {
    v2_errorBoundary: true,
    v2_headers: true,
    v2_meta: true,
    v2_normalizeFormMethod: true,
    v2_routeConvention: true,
    v2_dev: {
      port: process.env.HMR_SERVER_PORT || 8002,
    },
  },
  serverDependenciesToBundle: [
    /^remix-utils.*/,
  ],
};

 

Then, import cors from the remix-utils package and update the return in your loader function of your API route with the cors function, as per their docs linked above. It should look like the following:

 

api.get-user.jsx

 

import { json } from '@remix-run/node';
import { cors } from 'remix-utils/cors';

export async function loader({ request }) {

  const response = json({ body: 'data' });

  return await cors(request, response);
}

 

I am now successfully making requests from the theme which is running locally.

 

Danh11
Shopify Partner
62 2 20

Just to note - This actually still does not work when making the request via the Shopify app proxy while on local development. It does work ok when the theme is deployed and running from the Shopify server.

 

Although making requests to the app URL (with the tunnel running feeding traffic to the app running locally) does work in local development. So at least this makes it possible to develop locally. The app URL can be swapped out for the Shopify app proxy when it comes to deployment. This won't be a great dev experience if/when the app proxy signature is checked on the endpoint provided by the app, but that's a problem for another time.

myvirtualteams
Shopify Partner
28 0 9

Hey @Danh11 We're also stuck at cors error just i'm not creating a theme .
I have created an app using shopify remix.

 

Created an route in the app which will open in browser rather than on the store admin app dashboard.

 

Updated the remix config files like this 

if (
  process.env.HOST &&
  (!process.env.SHOPIFY_APP_URL ||
    process.env.SHOPIFY_APP_URL === process.env.HOST)
) {
  process.env.SHOPIFY_APP_URL = process.env.HOST;
  delete process.env.HOST;
}

/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
  publicPath: process.env.SHOPIFY_APP_URL + "/build/",
  ignoredRouteFiles: ["**/.*"],
  appDirectory: "app",
  serverModuleFormat: "cjs",
  includeRoutes: [
    require.resolve("./app/routes/api.jsx"), // Include your API route
  ],
  future: {
    v2_errorBoundary: true,
    v2_headers: true,
    v2_meta: true,
    v2_normalizeFormMethod: true,
    v2_routeConvention: true,
    v2_dev: {
      port: process.env.HMR_SERVER_PORT || 8002,
    },
  },
};

As you can see I've give the public path with url where our app will be hosting I've named it SHOPIFY_APP_URL but it'll be heroku url where our app is hosted.

 

We've to do it as when we use as proxy with default publivPath it try to access build files using shopify domain and there is error not found.

We've add that route as app proxy so we can access through the store.

 

Then we've hosted our app on the heroku and added that url like this heroku_url/app_route in app proxy.

Now we're geting files but getting cors error while running that url as app aproxy.

 

css file is able to load but the build js files are giving cors errors.

 

I dont able to get where i will add cors headers so my store can access the shopify app build files hosted on heroku can remove cors errors

 

 

My Virtual Teams
Danh11
Shopify Partner
62 2 20

I'm not really familiar with what you're trying to do there, sorry!

 

If you're trying to define a new route, that would be done with a jsx or tsx file named appropriately. For example, api.endpoint.jsx for the path of /api/endpoint. You can then use the cors package that I mentioned in my previous comment to add the headers to the response.

 

Unless I have misinterpreted what you're trying to achieve?