Teaching platforms sound like a good deal until you do the math. Teachable charges up to 5% per transaction on its free plan. Podia takes a cut too. Gumroad charges 10%. On $10,000 in course sales, you hand $500 to $1,000 to a platform you do not control, cannot fully customize, and that can limit your account at any time.
Building your own course platform used to mean months of work: auth from scratch, payment flows, content delivery, enrollment emails, student progress. With the Next.js SaaS boilerplate, most of that foundation is already done.
Strip a course site down to its core and you have four pieces:
The boilerplate ships the first two fully built. Auth is JWT-based with bcrypt password hashing -- you can separate instructor and student roles using the RBAC pattern already in the codebase. For payments, Stripe one-time payments handles checkout, webhooks, and database updates out of the box.
Content gating and lesson delivery are the product you build on top.
Before you write a line of schema, choose how you charge:
Stripe Checkout handles all three. The boilerplate ships with both subscription and one-time checkout flows -- you configure a price in the Stripe dashboard and point the checkout session at it. When payment succeeds, a Stripe webhook fires. Your webhook handler marks the user as enrolled and Resend sends a welcome email with the course link. That email flow follows the pattern in transactional email with Resend.
Content gating lives in the service layer, not in middleware. You add an enrollments table with user_id and course_id, then check enrollment before rendering any lesson:
// modules/enrollment/enrollment.service.ts
async function requireEnrollment(userId: string, courseId: string) {
const enrollment = await enrollmentRepo.find(userId, courseId);
if (!enrollment) throw new HttpError(403, 'Not enrolled');
}
Call this at the top of any server component that renders lesson content. Non-enrolled visitors get a 403 and you redirect them to the sales page. This mirrors the plan-gating pattern already in the codebase, so you are extending something you already understand.
The boilerplate does not ship a video player -- and you should not build one. Host videos on YouTube (unlisted links), Vimeo (with domain privacy), or Cloudflare Stream. Embed with a standard <iframe>. The boilerplate's Cloudinary integration handles downloadable worksheets, PDFs, and slide decks.
This is the right tradeoff for a first version. You own the enrollment records, the payment flow, and the student relationship. Those are what matter for your business. The video hosting is a solved, cheap commodity.
Building the course-specific pieces on top of the boilerplate gives you:
The only thing a hosted platform buys you that this does not is its built-in audience. If you have your own -- an email list, a social following, a community -- you do not need the platform. You need the infrastructure.
Say you sell a course at $297 and move 50 enrollments in your first launch.
The boilerplate pays for itself inside the first launch, and your student list belongs to you permanently.
Clone the Next.js SaaS boilerplate and you start with auth, Stripe, and email already wired. Add the enrollment table, the lesson pages, and the gating check -- and you have a course platform that costs you a fraction of what Teachable charges, while keeping every student relationship you build.