Approach
Postgres holds the model and access rules. Clients show honest loading, errors, and sync state. Local queues only when dropping offline would lose real work.
How I build
- 01
Rules in the database
Postgres enforces who may read and write; the UI does not guess policy.
- 02
Resilient clients
Retries and clear failures by default; device queue when offline would lose work.
- 03
Clear errors
Failures surface in the product and in telemetry so nobody is left guessing.
Work examples
The same ideas show up in different products. Each write-up explains the choices.
Rules I follow
Access rules belong in the database
RLS and storage rules keep tenant data apart. APIs stay thin and easier to check.
Offline-first is a product choice, not a default
When the use case needs it, work is saved locally first and an outbox sends it later. Many apps stay online-first with good cache boundaries and visible errors instead.
Realtime is scoped and optional
Realtime updates are scoped per tenant. The app only listens to what it needs.
Simple infrastructure beats clever hosting
Managed Postgres, auth, and storage reduce maintenance work. Less ops load is a feature.
When something fails, show it in the product
Sync state and error states belong in the product, not only in logs. Users should know what failed.
Patterns I use
Small system patterns I reuse when the product needs them.
Click a card to expand the diagram.
Sync & offline
Realtime to many clients
Async work & side effects
Client cache & server entry
Logs & errors
Stack by category
The tools grouped by what they are responsible for.
- Language
- TypeScript
TypeScript across the app, so data shapes and contracts are clear while building.
- UI layer
- React
- shadcn/ui
- Tailwind CSS
React, shadcn/ui, and Tailwind for small UI pieces that are easy to change.
- App shell
- Next.js
- TanStack
- Vite
Next.js for most apps. Vite and TanStack Router when a focused SPA is a better fit.
- Data
- PostgreSQL
- Supabase
PostgreSQL and Supabase for data, auth, storage rules, and tenant boundaries.
- Client data
- TanStack
- PWA
TanStack Query for server data. Zustand for UI state. IndexedDB and an outbox when work must sync later.
- Packages & CI
- pnpm
- Turborepo
pnpm by default. Turborepo when multiple apps need to share code.
- Ship & observe
- Vercel
- Sentry
Vercel to ship. Sentry, logs, and Postgres metrics to see what broke.


