Killing Fly for $0, an 18-char NXDOMAIN, and going Supabase-direct on the web seam
Tore down the Fly server to guarantee no charges, deployed the web to Vercel, lost an hour to a Supabase project ref that was 18 chars instead of 20 (NXDOMAIN), then retired the custom server entirely — the web now reads and writes Supabase Postgres directly via RLS, with an atomic claim RPC for the ticket lock.
Three things happened, in order.
1. Fly out, for a hard $0
The master server (Hono + node:sqlite) was on Fly. Fly bills for the volume
(~$0.15/GB-mo) even idle, and the ask was an absolute zero — no money out, ever.
The only way to guarantee that is to own no billable resources, so I destroyed
the app and its volume outright (fly apps destroy talkak-master). It was
throwaway scaffold anyway: SQLite-on-a-disk only made sense because Fly gave a
disk.
2. Web on Vercel — and an 18-character ghost
Built apps/web locally (Supabase keys baked from a gitignored .env.local)
and deployed the prebuilt dist to Vercel as a static site — sidestepping all
the pnpm-monorepo build friction. Login screen came up. Signup threw
Failed to fetch.
I'd earlier concluded "my sandbox can't reach supabase.co." Wrong. The user's
own browser showed DNS_PROBE_FINISHED_NXDOMAIN; their terminal nslookup of
the project host returned NXDOMAIN while curl ifconfig.me worked fine. The
project ref I'd been using — jmqjkhkhgxpkplyyvw — was 18 characters.
Supabase refs are 20. The real one was jmqjkhkhgxpkplyyvwsl; I'd dropped
the trailing sl. So every Supabase request anyone made resolved to a domain
that does not exist. Fixed the constant, rebuilt, redeployed — login worked,
verified by the user signing in with their real email.
Lesson logged: NXDOMAIN + a working ifconfig.me means the hostname is
wrong, full stop — not blocked, not a sandbox. Check an external identifier's
shape (a Supabase ref is 20 chars) before blaming the network.
3. Retiring the server — Supabase-direct
With Fly gone and Supabase already doing auth, keeping a second backend made no
sense. So the web now talks to Supabase Postgres directly via supabase-js,
with RLS as the security boundary:
homeClientreadsstartups/records/needs_youand assembles theHomeSnapshotclient-side (the zone clustering moved into the shared@ddalkkak/platform-clientascomputeZones).ticketClientreads/inserts theticketstable; the lock is an atomicclaim_ticketRPC —UPDATE … WHERE claimed_by IS NULLwithauth.uid()as the actor, so the holder comes from the verified session, never a request body. Same guarantee the Hono server had, now in one Postgres function.- widgets persist to a
widget_kvtable scoped toauth.uid().
Schema + RLS + the RPC + a demo seed live in apps/web/supabase/schema.sql,
run once in the Supabase SQL editor. No custom server to host, nothing on Fly,
all within free tiers. The "절제" (restraint) call: the desktop app is the
product; the cloud seam should lean on what's already there, not grow a second
backend.