Why this exists
Most "SaaS boilerplates" stop at a logged-in dashboard. The hard parts of a real product — multi-tenant isolation, Stripe edge cases, last-admin guards, edge-safe middleware — only show up after the demo screenshots are done. This starter goes there on purpose.
What's built
- Email/password auth with bcrypt and JWT sessions, edge-compatible middleware that protects
/dashboard/*. - Multi-tenant by design. Every server query is scoped by an active-organization context derived from a signed cookie and re-validated as a real membership on every request. Org IDs are never accepted from the client.
- RBAC with
admin/memberroles and acan(role, permission)helper. - Workspace switcher in the topbar; users belong to many organizations.
- Member invites, role changes, removal — with a "last admin" guard so a workspace can never become admin-less.
- Stripe-billed subscriptions per workspace — Checkout, Customer Portal, signature-verified webhooks.
- "Sync from Stripe" recovery — pulls truth from Stripe when a webhook was missed.
- Settings — display name, password change, workspace rename, danger-zone deletion that cancels the Stripe sub on the way out.
Technical choices worth calling out
Stripe is the source of truth, not the local DB
The duplicate-subscription guard, the "Sync from Stripe" button, and the webhook handler all derive state by asking Stripe — never the local DB. Webhooks can be dropped (especially in dev when stripe listen isn't running); the recovery paths assume that and reconcile from Stripe rather than papering over inconsistency.
Edge-safe middleware split
Auth.js v5 middleware runs in the Edge runtime where pg and bcrypt aren't available. lib/auth.config.ts is the edge-safe shared config used by proxy.ts; lib/auth.ts extends it with the credentials provider and Postgres lookup. Same callbacks, same JWT, two execution environments.
Last-admin guard, transactional
changeMemberRole and removeMember open a transaction, count current admins, and refuse the operation if it would leave the workspace admin-less. Means an admin can't lock themselves out by demoting themselves when they're the only one.
Outcome
The same building blocks ship into client SaaS products without rework — auth, billing, and tenant isolation that have been tested against the Stripe sandbox and held up under real failure modes (dropped webhooks, double-clicked Checkout buttons, mid-cancel role changes).