Cloudflare has become my go-to platform for hosting Remix and Next.js apps. It's affordable, offers a generous free tier, and has a strong brand. I use Remix to build a Shopify app, so hosting it on Cloudflare pages seemed like a natural fit.
However, there aren't many examples of how to deploy Shopify apps on Cloudflare, and relevant documentation is scarce, so it took me two days to integrate everything and get the local dev environment working smoothly.
These examples have been invaluable in helping me understand what needed to be done:
- https://github.com/cloudy9101/shopify-remix-cfpages
- https://github.com/rozenmd/d1-drizzle-remix-example
- https://github.com/dan-gamble/cloudflare-workers-remix-shopify
Instead of building another template that quickly becomes outdated, I will share my complete setup process step-by-step so you can understand it and adapt it yourself.
Why Use Cloudflare for Shopify Apps?
Cloudflare Pages offer serverless edge runtime, which lets you distribute your backend code close to your users without the initial cold start delay. This lets your app scale cost-efficiently while staying fast.
You will want to use a serverless database when you use edge functions. Otherwise, you're negating any gains from the edge functions. Cloudflare offers an SQLite-based solution for that called D1 Database, which is currently in public beta.
For storing Shopify sessions, we can use Cloudflare Workers KV, a key-value store that caches data on Cloudflare's CDN, ensuring extremely quick reads.
Cloudflare has everything we need to make a Shopify app super fast, and I love that everything is in a single place and uses a single CLI.
Step 0: Create a Partner Account and a Development Store
First, you'll need to sign up for a Shopify partner account if you don't already have one:
Then, you'll need to create a development store through your partner dashboard:
Just make sure you're creating a test store for an app:
Step 1: Initialize a Shopify App with Remix
To set up the initial code, you must create a Shopify partner account and follow the steps in the Shopify documentation here: https://shopify.dev/docs/apps/getting-started/create.
The CLI may ask you to authenticate first, but in the end, you should end up with your terminal looking like this:
When you run npm run dev
for the first time, you will be asked to create a new application and connect it to a development store:
Now quit the development server by pressing q
or ctrl+c
in your command line. We will customize the build tasks to use Cloudflare's Wrangler for local development.
Step 2: Update Config Files for Cloudflare
We had a choice of using either a Shopify CLI or Remix CLI to bootstrap our application. While the Remix CLI lets you generate the necessary configuration for hosting on Cloudflare, it doesn't bootstrap a Shopify app.
I think adding the Cloudflare configuration on top of a bootstrapped Shopify app is easier, and we can use the documentation and the Remix+Cloudflare Pages template to help us.
Install dependencies and update package.json
First, get the necessary Cloudflare dependencies from npm:
npm i @remix-run/cloudflare @remix-run/cloudflare-pages @shopify/shopify-app-session-storage-kv @remix-run/css-bundle
npm i -D @cloudflare/workers-types wrangler
Then, we need to add these to package.json
:
{
"engines": {
"node": ">=18.0.0"
},
"sideEffects": false,
"type": "module",
...
}
These lines are required to run your app on Cloudflare Pages. Additionally, replace the start
script with this:
"start": "wrangler pages dev ./public --live-reload --kv=SESSION",
We will be using wrangler pages dev
for local development and --kv=SESSION
binds the local storage version of Cloudflare KV to the context.env.SESSION
environment variable. We will make sure this binding is the same on prod later on.
Finally, feel free to delete predev
, setup
and prisma
scripts, as we will be using Drizzle ORM instead of Prisma.
Update Typescript Configuration
We need to ensure our app is compatible with ESModules. Replace your tsconfig.json
content with this code:
We've changed the module resolution to Bundler
instead of Node16
, added esModuleInterop
and bumped target and lib to ES2022.
We've also included remix.env.d.ts
, but we haven't yet created it, so do that now in your root directory:
Extend the Remix server
Create a file called server.ts
in your root directory with this code:
The important bit is the getLoadContext
line because it forwards the environment variables to our Remix loaders and actions.
We also need to update remix.config.js
with a custom startup command to use wrangler pages dev
for local development. Replace its contents with this:
A lot of the configuration settings come from the Cloudflare+Remix template: https://github.com/remix-run/remix/blob/main/templates/cloudflare-pages/remix.config.js. However, I've added a few important bits.
The shopify app dev
command opens a tunnel to your local server, updates the app URL in your partner dashboard, and sets the new URL along with the API key, secret, and scope as environment variables.
The problem is that Cloudflare workers don't receive environment variables - they must be bound. This is done by adding them to .dev.vars
file, and that's what the fs.writeFileSync
line does - it writes the latest env variable values to the .dev.vars
file every time the server is started.
Additionally, I've updated the command
used by remix dev
to call our npm start
which uses Wrangler to start the local Cloudflare development environment on the port the Cloudflare tunnel expects.
Finally, because we use the ESModules, we need to change the syntax of the default export.
With this configuration, you will be able to start your server, and you'll see that it's using wrangler
under the hood and it's being passed our environment variables as bindings:
Don't worry if you're getting errors when starting your app. We still need to take care of the Shopify part, but this configuration will enable us to deploy and run our project on Cloudflare.
Step 3: Update Shopify Setup for Cloudflare
We don't have access to process.env
anymore, but our Shopify configuration relies on it. We need to change that, so let's update the shopify.server.ts
with this content:
We wrap our Shopify object in a function that receives a context with all our environment variables. We'll be calling this function from our loaders and actions.
Additionally, I've replaced the sessionStorage
with KVSessionStorage
and pass it the bound id of the key-value store namespace. The declare module "@remix-run/cloudflare"
section helps us eliminate warnings due to the default context type being unknown
.
Update the usages of Shopify
Now that we changed the shopify.server.ts
file, any existing usages will break and need to be updated. Below are all of the changes you need to make.
In app/entry.server.tsx
Replace the contents with:
It's mostly the same as in Remix+Cloudflare template, but I've updated the handleRequest
to accept context as the last argument and pass it to initShopify
.
In app/root.tsx
Add the code necessary for css bundling:
In app/routes/*
We'll follow this pattern to fix most of the issues in routes:
- Get the
authenticate
method off of the Shopify object created with our new function. - Change the
node
imports tocloudflare
imports.
Do that in app/routes/auth.$.tsx
:
Do the same as above in app/routes/webhooks.tsx
, but additionally remove the usage of db
and instead remove session directly through shopify
, or simply replace the contents with this:
Do similar changes in app/routes/app.tsx
to fix the loader, making sure to change process.env
to context.env
:
And similarly in app/routes/app._index.tsx
to fix the loader and the action:
responseJson
is giving you type errors, just set it to any
:const responseJson = await response.json<any>();
Finally, fix the login
by taking it from the result of initShopify
and replacing node
imports with cloudflare
in app/routes/_index/route.tsx
and app/routes/auth.login/route.tsx
:
Now, if you run npm run dev
you should be able to visit the given preview URL, install your app, and see your app working:
Step 4: Using Drizzle with D1
First, we need to create a new D1 database using instructions in cloudflare docs:
The CLI gives us credentials to use to connect to the database. We need to create a wrangler.toml
config file in our root directory and paste them there (replace with your values):
I've added two extra lines, compatibility_date
so we don't need to pass it as CLI arguments and preview_database_id
which must match our binding so that D1 works with the local version of Cloudflare Pages.
--remote
to your wrangler
command. More info here: https://developers.cloudflare.com/d1/learning/local-development/Add Drizzle ORM
We'll loosely follow instructions in drizzle docs. First, let's install the necessary packages:
npm i drizzle-orm
npm i -D drizzle-kit
Then, let's create a new folder drizzle
in the root directory and create schema.ts
file inside with some tables to test our setup with:
And we also need to create a config file for drizzle called drizzle.config.ts
:
Finally, we need to create a way to access the database from our app, so let's modify the app/db.server.ts
file:
You'll notice that we use the same approach to get binding as we did with initShopify
. I also like to re-export schema from this file so it's easier to access it in the app.
Using drizzle in the app
We can now test our setup by writing and reading some test data.
In app/routes/app._index.tsx
let's insert some test data in the action and read it in the loader:
Let's output the test data somewhere we can see. Get the loader data at the top of the Index
component and output the test_data
somewhere on the page:
Before we test the app, we need to create the tables in the database. First, let's create the initial migration:
npx drizzle-kit generate:sqlite
Note the migration name, and run the command to update the wrangler database:
npx wrangler d1 execute dev-remix-cf-demo --local --file=./drizzle/migrations/0000_classy_kronos.sql
Finally, let's run npm run dev
to see our local database in action by clicking "Generate product."
shopify.app.toml
the scopes
are set to: scopes = "write_products"
. Afterwards run npm run shopify app config push
.You should see new values appearing, and they'll still be available locally if you restart your application:
Step 5: Deploying Your Shopify App
We got the local dev environment working, but we still need to deploy the production application. This will involve deploying the database, configuring the KV store, and deploying the application itself.
Deploying the D1 database
Even though we created tables and some data locally, the remote D1 database still has no tables or data. To initialize them, we need to run the migrations without the --local
flag:
npx wrangler d1 execute dev-remix-cf-demo --file=./drizzle/migrations/0000_classy_kronos.sql
If you visit your Cloudflare dashboard you should see the new tables appear:
Deploying the application
Go to Workers & Pages -> Overview and click "Create application" then go to "Pages" tab.
The simplest way from here is to push your application to a new GitHub repository and then "Connect to Git" in the Cloudflare dashboard. Everything will be taken care of automatically.
Alternatively, we can deploy through Wrangler CLI by pushing our static build files. To do that, create a new Cloudflare project first:
npx wrangler pages project create
Then, we can use wrangler pages deploy
to push our static files. It is helpful to create a script in package.json for this:
Running the command will create a new project in the Cloudflare dashboard:
Note the application URL. We will need to set it in our environment variables.
Configure KV store and D1 bindings
Go to "Workers & Pages" -> "KV" and "Create a namespace" with a name you'll easily identify:
Now go to "Overview," select your app, and go to "Settings" -> "Functions." Scroll down to "KV namespace bindings" and set SESSION
to the new namespace. It will be available on context.env.SESSION
just like in the development environment:
Further down below, bind your D1 database to DB
variable:
Bind Shopify environment variables
As the last bit of configuration in Cloudflare, we need to add Shopify env var bindings to our pages workers.
Go to your app's "Settings" -> "Environment variables" and for production environment set the variables as they are defined in .dev.vars
file or run npx shopify app env show
to see them in console. As SHOPIFY_APP_URL
set the URL from above, with https://
prefix:
Change app URLs in Shopify partner dashboard
Finally, we need to update our app settings in the partner dashboard. Change the app URL and callback URLs to use the new Cloudflare pages URL:
If we re-deploy the application by running npm run pages:deploy
we should be able to see the production version of our application.
In your dashboard's overview page click on "Select store" which will let you install the production app on your development store:
And if we test it in our development store, you'll see the requests going to the production URLs:
We can also verify that the production database is being used in our Cloudflare dashboard's D1 section:
Final cleanup
Now that we're not using prisma anymore, we can uninstall the related packages and remove prisma
directory.
We won't be deploying containers, so we can remove docker files.
Also, because we're changing the .dev.vars
file automatically, we can add it to .gitignore
and ignore our new build paths:
Template Repository
Setting up Shopify app development is tricky without instructions. This article should help. I've also created a repository for the template with all of the code from the article here: https://github.com/vincaslt/remix-cf-pages-d1-drizzle-shopify-app