DEV Community

Nathaniel Johnson
Nathaniel Johnson

Posted on • Updated on

remix.run + cloudflare workers + supabase + tailwind

Quick guide to setting up remix.run with cloudflare workers, tailwindcss, and supabase.

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
Enter fullscreen mode Exit fullscreen mode

Select Cloudflare Workers

remix cli selecting 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
Enter fullscreen mode Exit fullscreen mode

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\"",
Enter fullscreen mode Exit fullscreen mode

Now if you run yarn dev or npm run dev it should start your app on localhost:8787

local remix starter page

Tailwind

Install dependencies

npm install --save @headlessui/react @heroicons/react @tailwindcss/forms tailwindcss
Enter fullscreen mode Exit fullscreen mode

Add a build command for the css to package.json "scripts"

"dev:css": "tailwindcss -i ./styles/tailwind.css -o ./app/tailwind.css --watch",
Enter fullscreen mode Exit fullscreen mode

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\"",
Enter fullscreen mode Exit fullscreen mode

Add tailwind.config.js to the root of your app

module.exports = {
  content: ["./app/**/*.{ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [require("@tailwindcss/forms")],
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 },
  ];
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

remix home after adding tailwind

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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),
  });
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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[];
}
Enter fullscreen mode Exit fullscreen mode

And in your loader

export const loader: LoaderFunction = async ({ params }) => {
  const series = await listSeries();

  return {
    series
  }
});
Enter fullscreen mode Exit fullscreen mode

Discussion (2)

Collapse
justinnoel profile image
Justin Noel

Nice post, Nathan!

Can you explain why you chose Workers vs Pages?

With the new Pages Functions, you "should" be able to get most of this same functionality.

However, as this is all SSR, I'm not really sure of the benefit of Pages in general unless there are a fair number of static pages in your application.

Thoughts?

Collapse
nathanjohnson320 profile image
Nathaniel Johnson Author

That's a good question! When I set it up I thought I might need some custom caching which you can only do with workers but I ended up not needing that. If I re did things I'd use pages instead it has a more robust deployment pipeline.