A founder metrics dashboard for small SaaS teams: MRR, active customers, revenue churn, and ARPU computed live from the database, with a 12-month revenue chart, a sortable customer table, and a working add-customer flow.
Built on the Moonshift SaaS starter: Next.js (App Router), Drizzle ORM + libsql, Better Auth, Tailwind, and shadcn-style UI primitives.
bun install
bun run devThat is it. On the first request the app creates every table (auth + domain) and seeds 22 realistic demo customers plus a trailing 12 months of revenue. No migration step, no manual seeding.
To warm the database ahead of time (CI, demos):
bun run db:seed| Route | Purpose |
|---|---|
/ |
Overview: 4 KPI cards computed from the customers table, MRR area chart, at-risk watchlist, recent signups |
/customers |
Full account table (sortable by MRR), status badges, health meters, add-customer dialog backed by a server action |
/revenue |
Trailing 12-month MRR chart, new-vs-churned diverging bars, monthly breakdown table |
/settings |
Workspace + notification preferences (demo stub) |
/login, /signup |
Better Auth email + password (starter plumbing, untouched) |
- Data layer:
lib/db/domain.tsdefines thecustomersandrevenue_monthstables.lib/db/bootstrap.tscreates tables idempotently and seeds when empty; every server query path awaits it. - Queries:
lib/pulseboard/queries.tsholds all reads. KPIs are aggregated in SQL, not in JS over fetched rows. - Charts: hand-rolled inline SVG in
components/pulseboard/revenue-chart.tsx. Zero chart dependencies, server-rendered, theme-aware via CSS tokens. - Mutations:
app/(dashboard)/customers/actions.tsis a server action with zod validation and per-field error reporting. - Seed data:
lib/db/seed-data.tsgenerates dates relative to now, so the dashboard always shows a current-looking trailing year. The newest revenue month reconciles exactly with the paying customers' MRR sum.
Copy .env.example to .env if you want to point at a hosted libsql/Turso database or set a real BETTER_AUTH_SECRET. The defaults run fully local against file:./app.db.