Clone to Production in One Session: Building a Real Feature with AI
A walkthrough of cloning A2SaaS and using AI to build a complete feature — database table, API endpoints, and dashboard page — in a single sitting.
Most boilerplate demos stop at the install command. "Clone the repo, run the dev server, and you're ready to build!" Then what?
This post walks through what happens next. We'll clone A2SaaS and use Claude Code to build a complete feature: a projects system where users can create, view, and manage projects from the dashboard. Database table, API endpoints, and dashboard page — all in one session.
No hand-waving. Every step is real.
Step 0: Clone and Setup
npx create-a2saas my-app
cd my-app
npm run dev
You've got a running app with auth, payments, a dashboard, and a blog. The CLAUDE.md file is already in the root, so when you open this project with Claude Code, the AI knows the full architecture before you type anything.
Step 1: Add a Database Table
Instead of opening src/server/db/schema.ts and writing Drizzle schema by hand, we use the /add-table skill:
/add-table
Claude asks what you need:
What table would you like to add?
You describe it:
A projects table. Each project belongs to a user and has a name, description, and status (active, archived, completed). Include timestamps.
Claude reads the existing schema, follows the established patterns, and adds the table:
export const projects = pgTable("projects", {
id: varchar("id", { length: 128 })
.primaryKey()
.$defaultFn(() => createId()),
userId: varchar("user_id", { length: 128 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
name: varchar("name", { length: 256 }).notNull(),
description: text("description"),
status: varchar("status", { length: 32 }).notNull().default("active"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
It also adds the relations so Drizzle knows how projects connect to users. Then you push the schema:
npm run db:push
One table. One command. The AI didn't invent a new pattern — it copied the exact style from users and subscriptions.
Step 2: Add API Endpoints
Next, the /add-api skill:
/add-api
What API endpoint would you like to add?
You describe it:
CRUD endpoints for projects. Users should only see their own projects. Validate input with Zod.
Claude creates src/app/api/v1/projects/route.ts with GET and POST handlers, and src/app/api/v1/projects/[id]/route.ts with GET, PATCH, and DELETE handlers. Every endpoint follows the project's established pattern:
import { success, error, handleApiError } from "@/lib/api";
import { getUserId } from "@/lib/auth";
import { db } from "@/server/db";
import { projects } from "@/server/db/schema";
import { eq, and } from "drizzle-orm";
import { z } from "zod";
const createProjectSchema = z.object({
name: z.string().min(1).max(256),
description: z.string().optional(),
});
export async function GET() {
try {
const userId = await getUserId();
if (!userId) return error("Unauthorized", 401);
const userProjects = await db.query.projects.findMany({
where: eq(projects.userId, userId),
orderBy: (projects, { desc }) => [desc(projects.createdAt)],
});
return success({ projects: userProjects });
} catch (err) {
return handleApiError(err);
}
}
export async function POST(req: Request) {
try {
const userId = await getUserId();
if (!userId) return error("Unauthorized", 401);
const body = await req.json();
const input = createProjectSchema.parse(body);
const [project] = await db
.insert(projects)
.values({ ...input, userId })
.returning();
return success({ project }, 201);
} catch (err) {
return handleApiError(err);
}
}
Notice what the AI got right automatically:
- Imports from
@/lib/api— not inventing its own response format - Auth check with
getUserId()— using the abstraction, not calling Clerk directly - Zod validation — as required by the project conventions
- User scoping —
where: eq(projects.userId, userId)on every query - Error handling —
handleApiErrorwrapping, not try/catch withNextResponse.json
This is the CLAUDE.md doing its job. The AI isn't guessing at patterns — it's following documented conventions.
Step 3: Add a Dashboard Page
Finally, the /add-page skill:
/add-page
What page would you like to add?
You describe it:
A projects dashboard page at /dashboard/projects. Show a list of the user's projects with name, status, and created date. Include a button to create a new project with a dialog form.
Claude creates src/app/(app)/dashboard/projects/page.tsx — a server component that fetches projects and renders them with Shadcn components. It uses ensureUser() for auth, follows the existing dashboard layout patterns, and adds a client component for the create dialog with form validation.
The page works immediately because everything is wired to the same conventions: the API routes it calls, the auth it checks, the components it uses.
Why This Works
This isn't magic. It's three things working together:
1. CLAUDE.md Sets the Rules
Before Claude writes a single line, it has read the context file. It knows:
- Where to put files
- What patterns to follow
- What imports to use
- What to avoid
Without this, the AI would produce working code — but it might use NextResponse.json() instead of success(), or check auth with auth() instead of getUserId(), or skip Zod validation entirely.
2. Skills Provide Structure
The /add-table, /add-api, and /add-page skills aren't just shortcuts — they're structured prompts that guide the AI through a specific workflow. They ask clarifying questions, check existing code, and produce consistent results.
You could type out the same instructions manually. The skills just make it repeatable.
3. The Codebase is Scannable
A2SaaS has a flat structure with obvious conventions. The AI doesn't need to trace through five levels of abstraction to understand how auth works. It reads src/lib/auth/index.ts, sees getCurrentUser and getUserId, and knows the API.
Same for database queries, API responses, and page layouts. One file to read, one pattern to follow.
The Compound Effect
The real power isn't any single step. It's that each step builds on the last.
The table follows the schema conventions, so the API routes can query it correctly. The API routes follow the response conventions, so the dashboard page can call them predictably. The dashboard page follows the layout conventions, so it fits into the existing app seamlessly.
Every piece is consistent because the AI is following the same rules throughout. No style drift. No conflicting patterns. Just a codebase that looks like one person wrote it — even though an AI did most of the typing.
Try It Yourself
npx create-a2saas my-app
cd my-app
Open it with Claude Code and try building something. A tasks system. A notes feature. A team management page. The process is the same:
/add-table— Define your data/add-api— Create your endpoints/add-page— Build your interface
Each skill takes a minute or two. A complete feature in under ten minutes. And because everything follows the same conventions, the tenth feature is as clean as the first.
The best boilerplate isn't the one with the most features. It's the one where adding your own features is effortless.