Five modules, seven commands, one afternoon.
How an MIT-licensed staff platform deploys in under thirty minutes, on infrastructure you control, with no per-seat fees.
If you only read one paragraph.
StaffPortal is a Next.js application targeting Supabase + Vercel. Schema lives as numbered SQL migrations applied with the Supabase CLI. Three roles (owner, manager, employee) are enforced as row-level security policies in Postgres, not as application code. Five modules ship out of the box: auth, leave, attendance, payslips, and a folder-based document store. Deploy is seven commands documented in the README. Branding lives in one config file. No Docker, no telemetry, MIT licensed.
From clone to live, in seven commands.
git clone https://github.com/<org>/staffportal && cd staffportalClone the repopnpm installInstall dependenciessupabase link --project-ref <your-ref>Link to your Supabase projectsupabase db pushApply schema migrations and RLS policiescp .env.example .env.local && $EDITOR .env.localSet Supabase URL, anon key, service role key, brandingvercel --prodDeploy to Vercel (or any Next-compatible host)pnpm exec staffportal invite <you@company.com> --role ownerInvite yourself as owner; check email; sign inAnyone who has shipped a Next.js + Supabase app before will be live in under thirty minutes. The README walks through each step with screenshots. The hardest part is usually choosing a Supabase region.
A leave request, end to end.
The five modules.
Auth + profiles
Invite-only. Owner creates invitations from the admin module; the invitee receives a magic-link email; on first sign-in they pick a name and password (or stay magic-link only). Role assignment lives on profiles.role; manager-of relationships live on profiles.manager_id.
Leave
UK statutory defaults: 28 days including 8 bank holidays. Bank holidays auto-deducted per region (config). Leave types: annual, sick, parental, unpaid. Balances accrue monthly via a pg_cron job; year-end reset is a one-line SQL admin action.
Attendance
Single-button clock-in / clock-out, with an optional lunch toggle. Punches accumulate in attendance_punches; a derived view computes daily worked-minutes per user. Optional kiosk mode is a separate route, intended for tablet-on-wall deployments.
Payslips
Admin uploads PDFs to /payslips/{user_id}/{period}.pdf in Supabase Storage; metadata lands in the payslips table. Employees see only their own; downloads happen via short-lived signed URLs. Bulk upload supports a CSV mapping employee_id → file.
Documents
Folder tree with role-scoped read access. The documents table stores folder + file metadata; binaries go to Storage. RLS on the table controls who can see which folders. A typical setup: /policies (everyone), /managers (managers + owners), /owners (owners only), /personal/{user_id} (the user).
Why this, not that.
Next.js (App Router)
Single full-stack framework. Server components for read paths, server actions for mutations. One repo, one deploy, no separate API service.
Remix is fine but the Vercel-native deploy path on Next is one click. For a template aimed at non-engineer founders, that matters.
Supabase (Postgres + Auth + Storage)
One vendor for everything. RLS, magic-link auth, signed-URL storage, all on a generous free tier. The schema is portable Postgres if you ever want to leave.
Hand-rolled Postgres on a VPS plus NextAuth plus S3 plus a websocket server is four things to operate. The whole point is to not need an ops team.
Schema as Supabase migrations
Every table, policy, trigger lives in numbered SQL files. Forks can diff cleanly. Reproducible, version-controlled, applied with one CLI command.
Click-ops in the Supabase dashboard is unreproducible and lossy. Migrations are the only sustainable answer.
Tailwind + shadcn/ui
Defaults that look professional, components you can read, no theme system to fight. shadcn copies code into your repo so forks own their UI.
Material UI is heavy, opinionated, and hard to fork-customise without painful overrides.
React Email + Resend (optional)
Templates as React components, edit them like any other JSX. Resend is generous on free tier; switch to Postmark or SES with one env var change.
Hard-coded HTML strings rot. SMTP-only is operationally hostile.
Vercel deploy target
One-click deploy from GitHub. Branch previews. Free tier easily covers 50+ staff. The app is a vanilla Next.js project so it runs on any Next host.
Self-hosting on Fly or Railway is fine; the docs cover it. Default is Vercel because it has the lowest activation energy.
How forks and PRs work.
Local development
supabase start brings up a local Postgres + Auth stack. pnpm dev runs Next.js. supabase db reset reapplies migrations from scratch with seed data.
CI checks
GitHub Actions runs lint (Biome), typecheck (tsc), tests (Vitest), and a production build on every PR. Required to pass before merge.
Schema changes
New migrations are appended; never edit historical migrations. RLS changes go in the same migration as the schema change they protect.
Module additions
New modules live under app/(module-name) with their own migrations, server actions, and email templates. Core stays small; modules can be opt-in via config/modules.ts.
What it costs to run.
What is next.
Expenses module (opt-in)
Photo-capture + structured-extraction pipeline, optionally enabled. Same approval pattern as leave.
Timesheets
Project-based time tracking for back-office staff. Joins the existing payroll-export pattern.
Internal directory
Searchable photo + role + manager view. Already half-built into profiles.
Asset register
Track issued laptops, phones, keys per employee. Same UI conventions, separate module.
Want the engineering record?
The whitepaper covers the architecture, the design philosophy, and the lessons learned in detail.