Shipping a Live Product on Supabase and Cloudflare

Short answer: Supabase and Cloudflare make a strong pair for a small team shipping a real product. Postgres holds the data and the access rules, Supabase handles auth and real-time, and Cloudflare Workers serve the app close to users and do the jobs a browser should not. The lessons that mattered most for us were about discipline, not tools: write every schema change as a SQL migration, put security in the database, keep the edge Worker thin and let git be the only way to deploy.
These notes come from building Figment Gaming, a live platform with original browser games and companion tools for card and tabletop games. It is a React 19 single-page app on Cloudflare Workers, backed by Supabase Postgres with 300+ raw SQL migrations, real-time multiplayer rooms and AI opponents. The full breakdown is in the Figment Gaming case study.
Raw SQL Migrations, No ORM
Every change to the database, whether a table, a column, an index, a function or a policy, is a numbered SQL file checked into the repo and applied with the Supabase CLI. There are more than 300 of them now. We chose plain SQL over an ORM for control: Postgres features like row-level security policies, triggers and server-side functions are first-class citizens instead of escape hatches.
What made this sustainable:
- One shared schema. Every game and tool uses common patterns for cards, decks, drafts, saves, social features and user data, so a new game reuses tables instead of inventing its own.
- Sequential names. Migrations are numbered in order, so the history reads top to bottom and two branches cannot quietly collide.
- Expand, deploy, contract. The lesson we learned the hard way: never apply a breaking migration before the code that depends on it is live. Add the new column or function first, ship the code that uses it, and only then remove the old one. Once, a migration ran ahead of its code and saves broke until the deploy caught up. Since then, every breaking change goes in those three steps.

Row-Level Security Is the Real API
With Supabase, the browser talks to the database through an auto-generated REST API. That is fast to build on, and it means the database itself has to decide who can read and write each row. Row-level security policies do that job.
In practice, the tables that hold user data run with RLS on, and their policies are written in migrations like everything else. A few patterns carried most of the weight:
- Owner access. A user reads and writes their own decks, saves and settings, and nothing else.
- Participant access. Rows in a draft or duel room are readable by the people in that room, and by spectators only when the room is opened to the lobby.
- Server-side functions for anything sensitive. Actions that touch several tables or need extra checks run as Postgres functions, so the rules live in one place.
Supabase Auth ties it together with email and password, Google OAuth, role-based access and a path for anonymous visitors to become signed-in users without losing what they made. The one habit I would push on any team: when you add a table, write its policies in the same migration, and test them as a normal user, not as an admin.
Real-Time Rooms
Multiplayer drafting and duels run on Supabase Realtime over WebSockets. A room has a short code to join, a live lobby that others can spectate, shared state that every client keeps in step, and reconnection handling for when a phone drops off Wi-Fi.
The design rule that kept this manageable was that the database is the source of truth. Realtime messages tell clients that something changed, and the authoritative state lives in Postgres rows that RLS protects. A client that reconnects picks up from a snapshot of the current state, and a draft pauses on its own if the host drops. New game actions need both halves, the client-side engine and the server-side check, or they drift apart.
Keep the Edge Worker Thin
A Cloudflare Worker sits in front of the app. It serves the single-page app shell, answers the site's own API routes, runs scheduled jobs such as the notification email digest, and gives search engines and link previews real page titles and copy instead of an empty shell.
The best lesson here came from a mistake. At one point every request, including every script and image, ran through the Worker first. It worked, until the main bundle started taking about thirty seconds to download. Now only the routes that need logic go through the Worker, and static files come straight from Cloudflare's asset layer with long cache lifetimes. If a path does not need code, it should not touch the Worker.
Pre-Generated Images and Share Cards
Images are where a small team can lose speed fastest, so we decide ahead of time rather than at request time wherever we can:
- Static share cards. A build script renders a 1200 by 630 Open Graph image for each game hub and sub-page, so the common links always have a good preview.
- Dynamic share cards at the edge. For pages whose content changes, the Worker renders cards with Satori and Resvg compiled to WebAssembly, falling back to the static card for that game if anything fails.
- Housekeeping on a schedule. Share screenshots stored in Supabase are pruned weekly by a cron trigger, so storage does not grow forever.

Deploy from Git, and Nothing Else
Cloudflare builds and deploys the production branch straight from git. There is no manual deploy command in the normal workflow. That sounds like a small thing, but it removes a whole class of problems: what is live is always what is in the repo, rollbacks are a revert, and nobody ships from a laptop with uncommitted changes.
The gate before a push is the test suite: Vitest for unit tests and Playwright for end-to-end flows. After a deploy, we check that a new string actually appears in the live bundle rather than assuming the build went out.
What I Would Tell a Team Starting Today
- Write migrations in SQL from the first table, and never edit the database by hand in the dashboard.
- Turn on row-level security for every user-facing table on the day you create it.
- Treat the database as the source of truth for real-time state.
- Route only the paths that need code through the Worker.
- Generate images ahead of time when you can, and render at the edge only when you must.
- Make git push the deploy, and keep a test suite between you and production.
The Bottom Line
Supabase and Cloudflare let a small team run a production product with real users, real-time features and a global edge without a dedicated ops team. The tools get you started quickly. The habits above are what keep the product stable as it grows.
We bring the same stack and the same habits to client work, from internal tools and customer portals to data products with logins. If you are planning something similar, see our software development service or book a free consultation and we will talk through your architecture.


