Every "I built a SaaS in X hours" post hides the same lie by omission: the hours that made it possible happened before the timer started. The build I'm going to walk through — the AI School platform running on this site, with enrollment, lesson progress tracking, and downloadable certificates — took roughly 12 hours of Claude Code execution. It only went that fast because the specification work came first, and because the spec's most important sections were the things I refused to build.
That's the thesis: with Claude Code, preparation is the product. The model executes as well as your document specifies, and not one degree better.

Why most AI-assisted builds turn into Frankenstein software
I've watched developers open Claude Code with nothing but enthusiasm and a vague idea. They ask for "a user authentication system" and get something functional. Then "a dashboard," also functional. But when they connect the pieces, the auth tokens don't match the dashboard's expectations, the schemas conflict, and the endpoint naming follows two different conventions. Each request was optimized in isolation, disconnected from the whole — technically alive, stitched together from incompatible parts.
The second failure mode is scope creep through conversation. You start with a simple product and three hours later you're discussing real-time collaboration, because the AI is happy to engage with complexity — that's what it's good at. Without written constraints, every project drifts toward the enterprise version of itself.
A PRD solves both. It gives Claude the full picture upfront so every component is built with awareness of everything else, and it defines explicit boundaries so you ship the MVP instead of architecting the ultimate version. This is the same discipline my project's CLAUDE.md enforces on every task — state the domain, name the sibling pattern you're copying, list the concrete verify steps — just scaled up from a task to a product.
The build: a course platform with real SaaS mechanics
The AI School at /ai-school is a genuine MVP, not a demo: course catalog with categories, chapters and lessons, enrollment, per-lesson progress tracking, and certificates you can issue and download. Eleven routes cover the whole loop, from POST /ai-school/{slug}/enroll through POST .../lessons/{chapter}/{lesson}/complete to GET .../certificate/download.
The scope decision that made the 12-hour window possible is visible in one model. Here's the entire fillable surface of CourseEnrollment:
protected $fillable = [
'course_id',
'email',
'enrolled_at',
];
No user account. No password, no OAuth, no email verification flow, no profile page. Enrollment is an email address attached to a course, and progress hangs off the enrollment. That single cut deleted an entire authentication subsystem from the MVP — the subsystem that eats the first day of most SaaS builds — while keeping the mechanic that matters: a returning visitor picks up their progress and earns a certificate.
That's what a good PRD's "non-goals" section does. It's not a formality. It's where the schedule is won.
The PRD sections that actually pay
The document I write before an MVP build has five load-bearing parts. Vague versions of these produce vague code; every section should be detailed enough that implementation requires no further clarification.
Vision and non-goals. One paragraph of pitch, then the explicit cuts. For the AI School: no user accounts (email enrollment only), no payments, no video hosting (lessons embed external video), no quizzes. Real features, all of them — just not MVP features.
User journeys as narratives. Not wireframes — written paths. "A visitor lands on the course page, clicks Enroll, enters an email, and is returned to the course with progress tracking active. They open a lesson, mark it complete, and the course page shows 3 of 12 done." Every journey from first click to final state, including the unhappy ones: what happens when someone requests a certificate at 80% completion?
Complete schema. Every table, column, relationship, and index, written before any migration exists. The AI School runs on eight tables — courses, course_categories, course_chapters, course_lessons, course_enrollments, course_lesson_progress, course_certificates, course_notification_logs — and because the relationships were specified upfront, Claude generated migrations and Eloquent models that agreed with each other on the first pass. The schema section is where integration bugs go to die before they're born.
Endpoint contracts. Method, path, auth requirement, request shape, response shape, per endpoint. When frontend and backend are generated from the same contract, the integration step stops being a step.
Component states. For each UI piece: what data it shows, what actions it offers, what its loading, empty, and error states look like. "Enroll button: disabled while submitting, replaced by progress summary when enrolled" is one line in a PRD and thirty minutes of back-and-forth without one.
How the execution window actually went
The 12 hours split into passes, not a marathon. I ran Claude Code in plan mode first — the same plan-then-implement rhythm I've hardened into my CLAUDE.md workflow — feeding it the spec and having it restate scope before writing anything.
Then vertical slices, in dependency order: migrations and models first, verified with php artisan migrate:fresh and a factory smoke test. Then the catalog pages. Then enrollment. Then progress. Each slice got the project's standard verification before the next began — php artisan test --filter=<TestName>, vendor/bin/pint, PHPStan at level 8. That verification cadence is not ceremony; it's what lets the next slice trust the last one, which is the whole trick of moving fast with an agent.
Two rules from my daily practice carried most of the weight:
Build component by component with immediate integration. Generate the sidebar, wire it in, confirm navigation works, then generate the next piece. Integration issues surface while the context that caused them is still in the window. Batch-generating six components and connecting them at the end is how you discover three incompatible assumptions at the worst possible time.
Give the agent standing rules, not repeated corrections. My waves run with explicit instructions: follow existing sibling patterns in the codebase, don't ask for clarification on anything the spec already answers, stop at the end of the slice. With a detailed PRD, autonomous execution works. Without one, autonomous execution just means autonomous wrong decisions.
The part nobody shows: phases you deliberately don't build
Here's the receipt that this approach is real, straight from this repo's migration history:
2026_02_17_000000_create_course_tables.php
2026_03_09_194823_create_course_progress_and_certificates_tables.php
2026_03_30_100000_create_course_notification_tables.php
The MVP core — courses, chapters, lessons, enrollment — landed February 17. Progress tracking and certificates landed March 9, three weeks later, as their own planned wave. Email notifications (with tokenized unsubscribe links) landed March 30, three weeks after that. Each phase existed in the original document as a named non-goal with a rough shape, and each got specified in full detail only when its turn came.
That's the sustainable version of MVP development: not endless conversational scope creep, not features built without context. The PRD holds the future phases at arm's length until you're ready, and then each phase gets its own focused execution window. The March 9 wave even shows the honest texture of real work — two same-day follow-up migrations adding certificate PDF paths and verification, because the first pass of a phase is never the last.
The ROI math, honestly
The AI School spec took me the better part of a day to write. The execution window was roughly 12 hours — I didn't run a stopwatch, but the session history and the commit rhythm put it in that neighborhood. Without the spec, my honest estimate from 8+ years and 1,500+ projects of client work is that the same build lands somewhere between 40 and 60 hours: requirements re-explained per session, integration debugging, mid-stream architecture decisions cascading through the codebase.
Call it a 4-6x return on the preparation time, before counting the quality difference: PRD-driven code is more consistent because every component was generated with full system awareness, and the documentation your future self needs already exists — it's the spec.
The investment scales with complexity. A weekend experiment needs a one-page outline — the looser vibe-coding approach is fine there, and I use it for throwaways. An MVP with real users needs the full document. A client project needs it doubly, because the PRD quietly becomes the contract: what's included, what's excluded, what "done" looks like.
A template to start from
# [Product] PRD
## 1. Overview — pitch, target users, explicit non-goals
## 2. User Journeys — written narratives, happy and unhappy paths
## 3. Database Schema — tables, columns, relationships, indexes
## 4. API Contracts — method, path, auth, request/response shapes
## 5. UI Components — data, actions, loading/empty/error states
## 6. Integrations — third-party services, failure handling
## 7. Phases — what ships now, what's documented for later
Section 7 is the one people skip and the one that saves you. Writing "certificates: phase 2" is what lets you cut auth from phase 1 without feeling like you're building a toy.
If you want more structure around the execution side — how to sequence the waves and keep an agent inside its lane — I've written up the KODA framework I use for Claude Code builds and a ground-up Claude Code tutorial for the mechanics.
The question was never whether you can afford the time to write specifications. It's whether you can afford the 30 hours you'll spend without them. Pick your next product idea, write the document, cut everything that isn't the core loop, and let Claude Code execute while you make the decisions that actually need a human.
I scope and build MVPs like this for clients — spec first, fixed phases, working product at the end of the window. If you've got one that needs shipping, tell me about it.