The goal
A resume builder people can actually finish: write with help from an AI assistant, arrange sections by drag and drop, see the real PDF, and pay only when they need to (a Day Pass or a Pro monthly plan).
Stack and why
| Concern | Choice | Reason |
|---|---|---|
| App | TanStack Start (React 19, Vite, Nitro) | Type-safe file routing, server functions, one deployable |
| Data | SQLite + Drizzle ORM | Resumes are small documents; one file, auto-migrated on boot |
| Auth | Better Auth | Email plus optional Google OAuth, short-lived tokens, session rotation |
| AI | TanStack AI via OpenRouter | The writing assistant, swappable models |
| @react-pdf/renderer | The editor and the export share one layout engine | |
| Payments | Razorpay | Day Pass and subscription, with server-side webhook handling |
| Resend / SMTP | Verification and password reset |
Decisions worth writing down
The server is the source of truth. Resumes and their version history live in SQLite; the browser only keeps caches (thumbnails in IndexedDB) and preferences. Version history is a Pro feature, enforced on the server, not just hidden in the UI.
Graceful degradation. The app runs with no keys at all. If an API key is missing, that feature turns itself off: AI, email, Google sign-in and checkout. That makes local setup trivial and avoids half-broken production states.
One container on purpose. SQLite is single-writer and the rate limiter is in memory, so the app is designed as a single replica. That is a deliberate trade for simplicity and cost, documented rather than hidden.
Privacy-aware analytics. GTM and UTM capture only run after the cookie banner is accepted. Purchases are also sent server-side to GA4, once per payment.
SEO as a feature. Public guide pages are generated from a typed config, with sitemap, robots and JSON-LD built in, so the product can earn its own search traffic.
Plan limits
Free, Day Pass and Pro limits (daily AI writes, number of resumes) are environment-driven, so pricing experiments need no code change.
What I learned
Keeping the editor, preview and export on one rendering model removes a whole class of “it looked different in the PDF” bugs. And designing degraded modes first makes everything else easier to test.
