External Frontends with the Shopify CLI
The Shopify CLI can produce a frontend application for your Shopify app. If the built-in Gadget frontend doesn't work for you, you can use the frontend generated by the Shopify CLI as an external frontend for your Gadget backend application. This lets you still take advantage of Gadget's OAuth handling, scalable backend database, and robust webhook processing while using the frontend generated by the Shopify CLI.
Gadget's built-in frontend hosting
Gadget backend applications come with a built-in hosted frontend for Shopify app development. The built-in frontend automatically handles:
- hosting and deployment
- Shopify OAuth
- Shopify's security requirements for embedded applications
- installing and managing Shopify Polaris and the
@shopify/app-bridge
libraries.
Gadget's built-in frontend uses Vite, which is the same as the Shopify CLI, and means that the two are quite similar to work with.
Gadget recommends using the frontend that comes with your Gadget application for building Shopify apps, instead of using the frontend generated by the Shopify CLI. However, using the frontend codebase generated by the Shopify CLI as an external frontend may be necssary if you have special performance or hosting requirements.
Using the Shopify CLI frontend
To use Shopify's generated frontend as an external frontend, you will need to make a few changes to the code generated by the Shopify CLI. Instead of deleting the web
folder from your repository after generating a CLI app, keep it in place, and follow these steps:
- Update
web/index.js
to not implement Shopify OAuth, as Gadget handles OAuth and syncing data from the Shopify API. Instead,web/index.js
just needs to serve the frontend application with the correct security headers for Shopify.
Replace the contents of web/index.js
with the following:
web/index.jsJavaScript1// @ts-check2import { join } from "path";3import * as fs from "fs";4import express from "express";5import serveStatic from "serve-static";67const __dirname = new URL(".", import.meta.url).pathname;89const PORT = parseInt(process.env["BACKEND_PORT"] || process.env["PORT"], 10);10const STATIC_PATH =11 process.env["NODE_ENV"] === "production"12 ? `${__dirname}/frontend/dist`13 : `${__dirname}/frontend/`;1415const app = express();1617// return Shopify's required iframe embedding headers for all requests18app.use((req, res, next) => {19 const shop = req.query.shop;20 if (shop) {21 res.setHeader(22 "Content-Security-Policy",23 `frame-ancestors https://${shop} https://admin.shopify.com;`24 );25 }26 next();27});2829// serve any static assets built by vite in the frontend folder30app.use(serveStatic(STATIC_PATH, { index: false }));3132// serve the client side app for all routes, allowing it to pick which page to render33app.use("/*", async (_req, res, _next) => {34 return res35 .status(200)36 .set("Content-Type", "text/html")37 .send(fs.readFileSync(join(STATIC_PATH, "index.html")));38});3940app.listen(PORT);
- You can then delete the other example code that
@shopify/cli
created in theweb/
directory when it created your app if you like by running the following command in your app's root directory
Shellrm -f web/shopify.js web/product-creator.js web/gdpr.js
Finally, we can set up our Gadget Client and use the Provider to handle OAuth for our embedded app.
You need to install your Gadget dependencies in the web/frontend
directory of your Shopify CLI application! Change into this directory before running the following commands:
Shellcd web/frontend
- Install
local-ssl-proxy
in theweb/frontend
directory
npm install local-ssl-proxy
yarn add local-ssl-proxy
- Update the
dev
script inweb/frontend/package.json
tovite & local-ssl-proxy --source 443 --target 3005
.
1{2 // ...3 "scripts": {4 "build": "vite build",5 "dev": "vite & local-ssl-proxy --source 443 --target 3005",6 "coverage": "vitest run --coverage"7 }8}
If you are working with Windows, the dev
command above will not work. You will need to split it up into two separate commands and run
them separately. For example, "dev": "vite"
and "dev-proxy": "local-ssl-proxy --source 443 --target 3005"
.
This allows us to use our local frontend when doing development inside Shopify's admin, which uses HTTPS.
- Replace your
web/frontend/vite.config
file with the following code:
web/frontend/vite.config.jsJavaScript1import { defineConfig } from "vite";2import { dirname } from "path";3import { fileURLToPath } from "url";4import react from "@vitejs/plugin-react";56if (7 process.env["npm_lifecycle_event"] === "build" &&8 !process.env["CI"] &&9 !process.env["SHOPIFY_API_KEY"]10) {11 console.warn(12 "\nBuilding the frontend app without an API key. The frontend build will not run without an API key. Set the SHOPIFY_API_KEY environment variable when running the build command.\n"13 );14}1516const host = "localhost";17const port = 3005;1819export default defineConfig({20 root: dirname(fileURLToPath(import.meta.url)),21 plugins: [react()],22 define: {23 "process.env": JSON.stringify({24 SHOPIFY_API_KEY: process.env["SHOPIFY_API_KEY"],25 }),26 },27 resolve: {28 preserveSymlinks: true,29 },30 server: {31 host: host,32 port: port,33 hmr: {34 protocol: "ws",35 host: host,36 port: port,37 clientPort: port,38 },39 },40});
If you wish to change the port for your local server, make sure to modify both the port variable in web/frontend/vite.config
and the
target at the end of the dev
script in the web/frontend/package.json
. Note that the ports must be the same for the proxy to function
correctly.
Shopify CLI apps using Gadget don't need to use ngrok and instead run at https://localhost
. This vite config keeps vite's hot module reloading functionality working quickly without using ngrok which is faster and more reliable.
- You need to register the Gadget NPM registry for the
@gadget-client
package scope:
web/frontendShellnpm config set @gadget-client:registry https://registry.gadget.dev/npm
- The following npm modules are required when creating an app that will be embedded in the Shopify Admin:
npm install @gadgetinc/react @gadgetinc/react-shopify-app-bridge @gadget-client/example-app
yarn add @gadgetinc/react @gadgetinc/react-shopify-app-bridge @gadget-client/example-app
Make sure to replace `example-app` with your app's package name!
- To deploy your frontend using hosting platforms such as Vercel, Heroku or Netlify, you will need to add a new file
web/frontend/.npmrc
to help point to the Gadget registry.
@gadget-client:registry=https://registry.gadget.dev/npm
- The next step is to set up your Gadget client in the application. You can use this client to make requests to your Gadget application. You can create a new file in your project, and add the following code:
import { Client } from "@gadget-client/example-app";export const api = new Client();
- Now you need to set up the Provider in
web/frontend/App.jsx
. We can also use theuseGadget
hook to ensure we are authenticated before we make requests using the API. Here is a small snippet as an example:
web/frontend/App.jsxJavaScript1import {2 AppType,3 Provider as GadgetProvider,4 useGadget,5} from "@gadgetinc/react-shopify-app-bridge";6import { api } from "./api";78import { PolarisProvider } from "./components";910/**11 Gadget's Provider takes care of App Bridge authentication, you do not need Shopify's default AppBridgeProvider.12*/13export default function App() {14 return (15 <GadgetProvider16 type={AppType.Embedded}17 shopifyApiKey={process.env["SHOPIFY_API_KEY"]}18 api={api}19 >20 <PolarisProvider>21 <EmbeddedApp />22 </PolarisProvider>23 </GadgetProvider>24 );25}2627// This is where we make sure we have auth'd with AppBridge28// Once we have authenticated, we can render our app!29// Feel free to use the default page navigation that Shopify's CLI sets up for you30// example here - https://github.com/gadget-inc/examples/blob/main/packages/shopify-cli-embedded/web/frontend/App.jsx31function EmbeddedApp() {32 // we use `isAuthenticated` to render pages once the OAuth flow is complete!33 const { isAuthenticated } = useGadget();34 return isAuthenticated ? (35 <span>Hello, world!</span>36 ) : (37 <span>Authenticating...</span>38 );39}
If you are looking for examples of how to use our API client, visit our examples repository.
To use App Bridge components from Shopify you will need the useGadget
React hook. More information about the hook can be found in the
useGadget React hook docs.
Next steps
Once you've updated your Shopify CLI web
folder to function as an external frontend, you can start building your app! See the Shopify Connection guide for more information on building apps for Shopify with Gadget.
Deployment
If using the Shopify CLI's web
folder as the frontend for your application, you'll need to deploy it to a hosting platform. See the External Deployment guide for more information on deploying your frontend elsewhere.