remix.run + cloudflare workers + supabase + tailwind
Setup
Miniflare will only work with node >=16.7 so make sure you have a compatible node version installed before this
Start up the create-remix cli
npx create-remix@latest
Select Cloudflare Workers

You can use typescript or javascript. For this I'm using typescript.
Add concurrently to build the css, worker, and remix dev at the same time. Also at dotenv for environment variable injection locally (don't commit your .env). You also need to add the serve package because it doesn't get added by the create script for some reason.
npm install --save-dev concurrently dotenv @remix-run/serve
Update the dev script to concurrently build and run the worker locally
"dev": "concurrently \"node -r dotenv/config node_modules/.bin/remix dev\" \"npm run start\"",
Now if you run yarn dev
or npm run dev
it should start your app on localhost:8787

Tailwind
Install dependencies
npm install --save @headlessui/react @heroicons/react @tailwindcss/forms tailwindcss
Add a build command for the css to package.json "scripts"
"dev:css": "tailwindcss -i ./styles/tailwind.css -o ./app/tailwind.css --watch",
Update the "dev" script in package.json to concurrently build the css, remix, and worker
"dev": "concurrently \"npm run dev:css\" \"node -r dotenv/config node_modules/.bin/remix dev\" \"npm run start\"",
Add tailwind.config.js to the root of your app
module.exports = {
content: ['./app/**/*.{ts,tsx}'],
theme: {
extend: {},
},
plugins: [require('@tailwindcss/forms')],
}
Create a styles directory with the base tailwind css in it so the dev:css command will process it
/* styles/tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Now in the app/root.tsx we need to import and use the styles
import styles from './tailwind.css'
export function links() {
return [
// This is optional but is how to add a google font
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css?family=Open+Sans',
},
{ rel: 'stylesheet', href: styles },
]
}
In the root.tsx if we wrap the <Outlet />
in some tailwind styles it should display properly
<div className="relative bg-white overflow-hidden">
<div className="mt-4">
<Outlet />
</div>
</div>

Supabase
I won't go into much of the details on this but the below setup should get your cloudflare worker running with supabase. The main issues I ran into are that cloudflare workers don't have XMLHTTPRequest defined so you have to bind a fetch variable. Also the environment variables are globals not the usual process.env.<VAR_NAME>
.
Step one is to install supabase
npm install --save @supabase/supabase-js
Then add your supabase url and anon key to cloudflare secrets with wrangler. You can add them to your .env locally and they will get injected the same way.
wrangler secret put SUPABASE_URL
...enter the url
wrangler secret put SUPABASE_ANON_KEY
...enter the key
Now we need to create a client that will use the right environment variables and fetch to work.
// app/db.ts
import { createClient } from '@supabase/supabase-js'
export const supabase = () => {
// Globals are from cloudflare secrets
return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
fetch: fetch.bind(globalThis),
})
}
To fix the typescript errors on the SUPABASE_URL and SUPABASE_ANON_KEY environment variables you'll need to make a bindings.d.ts as mentioned here: https://github.com/cloudflare/workers-types#using-bindings
export {}
declare global {
const SUPABASE_ANON_KEY: string
const SUPABASE_URL: string
}
With that in place you can use it in your type files i.e.
// app/series.ts
import { Season } from './season'
import { supabase } from './db'
export type Series = {
index: number
title: string
seasons: Season[]
}
export async function listSeries(): Promise<Series[]> {
const { data } = await supabase().from('series').select(`
index,
title,
seasons (
index
)
`)
return data as Series[]
}
And in your loader
export const loader: LoaderFunction = async ({ params }) => {
const series = await listSeries()
return {
series,
}
}