Architecture decisions, real bugs, and what I would do differently if I started over tomorrow.

Small-to-mid landlords in India manage multiple properties across spreadsheets, WhatsApp chats, and paper receipts. They cannot track rent payments, pending maintenance, or tenant history without stitching together four or five tools. Existing SaaS is either too enterprise-heavy, or it skips Indian-market specifics like Razorpay and regional communication.
The hard part is not the CRUD — it is multi-tenancy. One owner's data must never bleed into another's, and the tenant invite flow has to be airtight while still being friendly to non-technical users.
A pragmatic Next.js + Hono stack — no edge runtime, no exotic database. The whole thing is intentionally boring on the outside so the multi-tenancy logic can be careful on the inside. Shared packages live in a Turborepo alongside Schooly.
Switched to Hono's hc client for end-to-end type safety. Return types are inferred straight from the router definition — no separate type sharing, and a whole category of API contract bugs disappears.
Middleware-only auth is not enough — it does not protect RSC data fetching. So: middleware (redirect), server-component layout (re-validate session), client SessionProvider (role gating). Belt + suspenders + a third belt.
Started on D1/SQLite for the edge convenience, then migrated. Complex multi-tenant JOINs perform better on Postgres, and the relational constraints I lean on were a real concern at scale.
Middleware was blocking unauthenticated invite-acceptance routes — new tenants could not even reach sign-up. Fix: redesign the matcher to allow public invite paths while still guarding every dashboard route.
BETTER_AUTH_URL in the wrong env sectionProduction URL resolution silently failed because the variable was in the server-only env section. Moving it to the client-accessible env prefix was a non-obvious fix that took a long time to track down.
A backend audit found tenant queries loading every row and filtering in JS instead of with WHERE. Rewrote with proper Drizzle where + indexed columns. Estimated 90%+ query-time reduction.
I spent weeks in a local-only environment. Real-domain deploys expose CORS, auth redirects, and env-var issues much earlier — and these are far easier to debug when you are not also debugging the feature code.
“The real lesson was not the bugs — it was that shipping early is its own kind of test, and skipping it is the most expensive form of perfectionism.”— Parm, in hindsight