Membership Platform
A subscription membership platform for a coaching business — curriculum delivery, live coaching, and Stripe billing.
- Role
- AI Engineer
- Year
- 2026
- Status
- In production
A membership platform for a coaching business. It delivers a structured curriculum, books live coaching sessions, and takes subscription payments. Members work through modules at their own pace; the business runs the whole thing — content, events, pricing, support — from an admin surface inside the same app.
The interesting problems were never the CRUD. They were the places where money, access and identity meet: what happens when somebody pays and then closes the tab, how an owner can run the business without being able to lock out the developer maintaining it, and how to make failures loud enough that nobody silently loses the access they paid for.
Architecture
Next.js App Router on Vercel, Supabase for Postgres, auth and storage, Stripe for billing. JWT validation happens at the edge, in middleware, before a request reaches a page. Above that sit three guards that compose: one requires a session, one requires an active subscription, one additionally requires the welcome series to be finished. Row-level security in Postgres enforces the same rules a second time, so a missed guard is a bug rather than a breach.
Access has three tiers — member, owner and developer — and the only power reserved to developer is granting and revoking access. That single boundary means the person running the business cannot accidentally lock out the person maintaining it, and a compromised owner account cannot mint new admins. The tier and the older is_admin boolean are kept honest by a CHECK constraint: a write that sets one without the other is rejected by the database, rather than trusted to be caught in review.
Checkout runs two flows. A cold one for visitors who pay before they have an account, and a warm one for members who already do. The cold flow creates the account after payment, which leaves an obvious hole — the buyer who closes the tab. The webhook is the safety net: it provisions the user passwordless and sends a recovery email. It also answers 500 on internal failure so Stripe retries. The previous behaviour returned 200 to everything, which meant a transient database error could quietly cost a paying customer their access and leave nothing behind to find it by.
Platforms used
Application
- Next.js 16 · App Router
- React 19
- TypeScript (strict)
- Tailwind CSS v4
- Turbopack
Data & auth
- Supabase Postgres
- Supabase Auth
- Row-level security
- SQL migrations
Payments
- Stripe Embedded Checkout
- Customer Portal
- Webhooks
- Trials & dunning
Operations
- Vercel
- Sentry
- Model Context Protocol
Features
-
Curriculum and lesson player
Modules and lessons covering video, PDF and live-call formats, with a welcome series that gates the rest. Progress is tracked per member, so people return to where they stopped rather than to a table of contents.
-
Live coaching and workshops
Session booking with RSVP, a calendar view, and an archive of workshop replays — so the live element still has value to somebody who joins six months late.
-
Subscription billing
Monthly and annual plans through embedded checkout, optional free trials, and self-service management via the Stripe Customer Portal. Subscription state drives access directly: trialing and past-due both keep the door open, cancellation and failed renewal close it.
-
Admin surface
Curriculum, events, library, sales page, pricing, support messages, member directory and access tiers, all editable in-app. Two refusals are enforced server-side and cannot be clicked past: you cannot demote yourself, and you cannot remove the last developer.
-
Admin over MCP
An MCP server exposes the admin routes as tools, so the business owner can run the platform from a conversation — add a workshop, check who lapsed this month, change a price. It is deliberately a thin client: every tool is the same HTTP route the panel uses, called as that user with a personal access token. No shared key, no database access. Whatever the account cannot do in the panel, it cannot do here either.
-
Error monitoring that survives ad blockers
Server errors are captured through the framework error hook, which covers exceptions thrown inside route handlers — the case that matters, since a failing payment webhook otherwise disappears into ephemeral logs. Browser events are tunnelled through a same-origin path rather than sent to the vendor domain, because host-based blocking would otherwise drop exactly the client-side errors worth seeing.
Screens
Client identifiers in these screenshots have been redacted.