Use Cases (अपडेट: 2/6/2026)

Claude Code से SaaS boilerplate बनाएं: Next.js auth, billing, tenant और tests

Claude Code से paid SaaS starter बनाएं: Next.js, auth, billing, tenant, audit logs, tests और launch checklist।

Claude Code से SaaS boilerplate बनाएं: Next.js auth, billing, tenant और tests

SaaS boilerplate का मतलब है paid web product के लिए reusable आधार: authentication, billing, tenants, roles, email, dashboard, admin screen, audit logs, environment variables, tests, documentation और deployment checklist। Claude Code इस आधार को तेजी से बना सकता है, लेकिन अगर इसे paid product या sellable template बनाना है, तो demo से ज्यादा मजबूत boundaries चाहिए।

सबसे खतरनाक shortcut है यह मान लेना कि “app चल रही है, इसलिए तैयार है”। Tenant का मतलब है एक ही application के अंदर अलग company, workspace या customer account। अगर tenant check कमजोर है, तो एक customer दूसरे customer का data देख सकता है। RBAC यानी role-based access control: owner, admin, billing, member और viewer को एक जैसे permissions नहीं मिलने चाहिए। Audit log का मतलब है किसने, कब, क्या किया इसकी record trail। इसके बिना support, incident response और enterprise sales मुश्किल हो जाते हैं।

यह guide दिखाती है कि Claude Code को कैसे prompt करें और Next.js App Router, TypeScript, Prisma, Stripe और Resend के साथ ऐसा SaaS starter कैसे बनाएं जो paid product या productized template बन सके। Implementation करते समय official docs को source of truth मानें: Claude Code docs, Next.js docs, Auth.js, Prisma schema docs, Stripe webhooks, Resend docs और OWASP Authentication Cheat Sheet

ClaudeCodeLab पर आगे पढ़ने के लिए secure authentication, RBAC implementation, Zod validation और Claude Code API development देखें।

पहला सवाल यह नहीं है कि Claude Code कौन से components generate करेगा। पहला सवाल है कि जो व्यक्ति यह starter खरीदेगा, वह क्या launch कर पाएगा। बहुत सारे screens वाला लेकिन weak tenant boundary वाला template कम valuable है। छोटा template भी बेहतर है अगर उसमें tests, documentation और review rules साफ हों।

Use caseजरूरी baseMonetization path
Solo micro-SaaSOAuth login, personal plan, Stripe Checkout, usage dashboardसरल monthly subscription
B2B team toolTenants, invites, roles, billing owner, audit logsper-seat या per-workspace pricing
Member content या template portalbuyer role, download history, email notifications, adminpaid template packs और courses
Internal AI workflow toolSSO, approvals, IP या domain policy, operation logsimplementation consulting

Boilerplate को legal, tax, privacy या security review का replacement न बताएं। Stripe tax settings, refunds, terms of service, privacy policy, data retention, customer support और permission review अभी भी human review मांगते हैं। Claude Code implementation speed बढ़ाता है, business responsibility नहीं हटाता।

flowchart LR
  A["Marketing site"] --> B["Auth"]
  B --> C["Tenant and roles"]
  C --> D["Dashboard"]
  C --> E["Billing"]
  C --> F["Admin"]
  D --> G["Audit logs"]
  E --> G
  F --> G
  G --> H["Tests and release checklist"]

Claude Code को CLAUDE.md contract दें

अगर आप सिर्फ “SaaS app बना दो” लिखते हैं, तो Claude Code UI, API routes, database models और business rules को एक बड़े diff में मिला सकता है। CLAUDE.md task को contract बनाता है। सरल भाषा में, harness वह काम करने की जमीन है जो agent को reviewable बदलाव करने में मदद करती है।

# CLAUDE.md

## Product goal
Build a paid SaaS starter that can be reused for real products.
Do not claim the starter removes legal, tax, privacy, or security review.

## Stack
- Next.js App Router with TypeScript
- Prisma and PostgreSQL
- Auth.js for OAuth/session integration
- Stripe Checkout and billing webhooks
- Resend for transactional email
- Vitest and Playwright for acceptance tests

## Required boundaries
- Every business record belongs to a tenantId.
- Never trust tenantId from the browser without checking membership.
- Roles are OWNER, ADMIN, BILLING, MEMBER, VIEWER.
- Billing routes require OWNER or BILLING.
- Admin routes require OWNER or ADMIN.
- State-changing routes write an audit log.
- Secrets must be read through src/lib/env.ts and never hardcoded.
- Include tests for forbidden tenant access and webhook idempotency.

## Review output
After edits, list changed files, commands run, risks, and manual checks.

ये rules common shortcuts रोकते हैं: browser से आया tenantId trust करना, secrets को code में रखना, member को billing route देना, या state change बिना audit log के करना।

Starter को responsibility के हिसाब से organize करें

छोटा demo एक route folder में रह सकता है। SaaS नहीं। Auth, billing, team management, email और audit जल्दी overlap करते हैं, इसलिए folder structure में boundaries दिखनी चाहिए।

src/
  app/
    (marketing)/
    (auth)/
    (dashboard)/dashboard/page.tsx
    (admin)/admin/page.tsx
    api/
      billing/checkout/route.ts
      billing/webhook/route.ts
      tenants/invite/route.ts
  components/
    dashboard/
    pricing/
    ui/
  lib/
    auth.ts
    env.ts
    prisma.ts
    tenant.ts
    audit.ts
    email.ts
    stripe.ts
  tests/
    acceptance/saas.spec.ts
prisma/
  schema.prisma

इस structure से prompt भी precise बनते हैं। आप कह सकते हैं, “सिर्फ billing webhook और related tests update करो”, पूरी app को छूने की जरूरत नहीं।

Prisma schema में tenant boundary रखें

नीचे schema practical minimum है। Tenant company, workspace या customer account है। Membership user को tenant और role से जोड़ता है। Subscription Stripe state रखता है। AuditLog important changes record करता है।

// prisma/schema.prisma
enum Role {
  OWNER
  ADMIN
  BILLING
  MEMBER
  VIEWER
}

enum Plan {
  FREE
  STARTER
  PRO
}

enum SubscriptionStatus {
  TRIALING
  ACTIVE
  PAST_DUE
  CANCELED
}

model User {
  id          String       @id @default(cuid())
  email       String       @unique
  name        String?
  memberships Membership[]
  auditLogs   AuditLog[]
  createdAt   DateTime     @default(now())
}

model Tenant {
  id             String        @id @default(cuid())
  name           String
  slug           String        @unique
  plan           Plan          @default(FREE)
  memberships    Membership[]
  subscription   Subscription?
  auditLogs      AuditLog[]
  createdAt      DateTime      @default(now())
  updatedAt      DateTime      @updatedAt
}

model Membership {
  id        String   @id @default(cuid())
  userId    String
  tenantId  String
  role      Role     @default(MEMBER)
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  tenant    Tenant   @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now())

  @@unique([userId, tenantId])
  @@index([tenantId, role])
}

model Subscription {
  id                   String             @id @default(cuid())
  tenantId             String             @unique
  stripeCustomerId     String             @unique
  stripeSubscriptionId String?            @unique
  status               SubscriptionStatus @default(TRIALING)
  priceId              String?
  currentPeriodEnd     DateTime?
  tenant               Tenant             @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  updatedAt            DateTime           @updatedAt
}

model AuditLog {
  id        String   @id @default(cuid())
  tenantId  String
  actorId   String?
  action    String
  metadata  Json?
  tenant    Tenant   @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  actor     User?    @relation(fields: [actorId], references: [id])
  createdAt DateTime @default(now())

  @@index([tenantId, createdAt])
}

Real product में invoices, addresses, usage counters, API keys, data retention और support notes भी जोड़ने पड़ सकते हैं। पहले दिन सब कुछ model करना जरूरी नहीं है। जरूरी यह है कि tenant boundary शुरू से ही अनदेखी न हो।

Environment variables को validate करें

Secrets code, screenshot या commit में नहीं होने चाहिए। .env.example में सिर्फ names रखें; real values Vercel, Cloudflare, AWS, GitHub Actions या आपके platform में रखें। env.ts missing keys को जल्दी पकड़ता है।

// src/lib/env.ts
import { z } from "zod";

export const env = z
  .object({
    DATABASE_URL: z.string().url(),
    AUTH_SECRET: z.string().min(32),
    NEXT_PUBLIC_APP_URL: z.string().url(),
    STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
    STRIPE_WEBHOOK_SECRET: z.string().startsWith("whsec_"),
    STRIPE_PRICE_STARTER: z.string().min(1),
    RESEND_API_KEY: z.string().startsWith("re_"),
    EMAIL_FROM: z.string().email(),
  })
  .parse(process.env);

Tenant और role server पर check करें

Browser tenantId भेज सकता है, लेकिन server को उस पर भरोसा नहीं करना चाहिए। Business data पढ़ने या बदलने से पहले server पर Membership load करें।

// src/lib/tenant.ts
import { Role } from "@prisma/client";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";

const roleRank: Record<Role, number> = {
  VIEWER: 1,
  MEMBER: 2,
  BILLING: 3,
  ADMIN: 4,
  OWNER: 5,
};

export async function requireTenant(tenantId: string, minimumRole: Role = "MEMBER") {
  const session = await auth();
  if (!session?.user?.id) redirect("/login");

  const membership = await prisma.membership.findUnique({
    where: {
      userId_tenantId: {
        userId: session.user.id,
        tenantId,
      },
    },
    include: { tenant: true },
  });

  if (!membership || roleRank[membership.role] < roleRank[minimumRole]) {
    throw new Error("Forbidden tenant access");
  }

  return {
    userId: session.user.id,
    tenant: membership.tenant,
    role: membership.role,
  };
}

Common failure यह है कि tenantId को hidden input में रखकर update में direct use कर लिया जाए। Hidden input secret नहीं होता। हर write operation में server-side Membership check करें।

Stripe Webhook को repeatable event मानें

Webhook external notification है। यह एक से अधिक बार आ सकता है या late आ सकता है। Production-ready starter में signature verification, metadata.tenantId, upsert और audit log होना चाहिए।

// src/app/api/billing/webhook/route.ts
import Stripe from "stripe";
import { headers } from "next/headers";
import { NextResponse } from "next/server";
import { SubscriptionStatus } from "@prisma/client";
import { env } from "@/lib/env";
import { prisma } from "@/lib/prisma";

const stripe = new Stripe(env.STRIPE_SECRET_KEY);

function toStatus(status: Stripe.Subscription.Status): SubscriptionStatus {
  if (status === "active") return "ACTIVE";
  if (status === "past_due") return "PAST_DUE";
  if (status === "canceled") return "CANCELED";
  return "TRIALING";
}

export async function POST(request: Request) {
  const body = await request.text();
  const signature = (await headers()).get("stripe-signature");

  if (!signature) {
    return NextResponse.json({ error: "Missing Stripe signature" }, { status: 400 });
  }

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, signature, env.STRIPE_WEBHOOK_SECRET);
  } catch {
    return NextResponse.json({ error: "Invalid Stripe signature" }, { status: 400 });
  }

  if (
    event.type === "customer.subscription.created" ||
    event.type === "customer.subscription.updated" ||
    event.type === "customer.subscription.deleted"
  ) {
    const subscription = event.data.object as Stripe.Subscription;
    const tenantId = subscription.metadata.tenantId;

    if (!tenantId || typeof subscription.customer !== "string") {
      return NextResponse.json({ error: "Missing tenant metadata" }, { status: 400 });
    }

    await prisma.subscription.upsert({
      where: { tenantId },
      create: {
        tenantId,
        stripeCustomerId: subscription.customer,
        stripeSubscriptionId: subscription.id,
        status: toStatus(subscription.status),
        priceId: subscription.items.data[0]?.price.id,
        currentPeriodEnd: new Date(subscription.current_period_end * 1000),
      },
      update: {
        stripeSubscriptionId: subscription.id,
        status: toStatus(subscription.status),
        priceId: subscription.items.data[0]?.price.id,
        currentPeriodEnd: new Date(subscription.current_period_end * 1000),
      },
    });

    await prisma.auditLog.create({
      data: {
        tenantId,
        action: `stripe.${event.type}`,
        metadata: { eventId: event.id, subscriptionId: subscription.id },
      },
    });
  }

  return NextResponse.json({ received: true });
}

Email और audit को shared helpers में रखें

Invites, failed payments, password reset और admin alerts email use करते हैं। Billing changes, role changes और tenant settings audit logs मांगते हैं। Shared helpers से product review आसान होता है।

// src/lib/email.ts
import { Resend } from "resend";
import { env } from "@/lib/env";

const resend = new Resend(env.RESEND_API_KEY);

export async function sendTenantInviteEmail(input: {
  to: string;
  tenantName: string;
  inviteUrl: string;
}) {
  return resend.emails.send({
    from: env.EMAIL_FROM,
    to: input.to,
    subject: `${input.tenantName} invited you`,
    html: `<p>You were invited to ${input.tenantName}.</p><p><a href="${input.inviteUrl}">Accept invite</a></p>`,
  });
}
// src/lib/audit.ts
import { prisma } from "@/lib/prisma";

export async function writeAuditLog(input: {
  tenantId: string;
  actorId?: string;
  action: string;
  metadata?: Record<string, unknown>;
}) {
  return prisma.auditLog.create({
    data: {
      tenantId: input.tenantId,
      actorId: input.actorId,
      action: input.action,
      metadata: input.metadata,
    },
  });
}

UI polish से पहले acceptance tests तय करें

Acceptance test यह जांचता है कि feature user की नजर से acceptable है या नहीं। Paid SaaS starter में refusal paths अक्सर happy path जितने ही जरूरी होते हैं।

// tests/acceptance/saas.spec.ts
import { test, expect } from "@playwright/test";

test("member cannot open billing settings", async ({ page }) => {
  await page.goto("/test-login?role=MEMBER");
  await page.goto("/dashboard/acme/billing");
  await expect(page.getByText("Forbidden")).toBeVisible();
});

test("billing user can open billing settings", async ({ page }) => {
  await page.goto("/test-login?role=BILLING");
  await page.goto("/dashboard/acme/billing");
  await expect(page.getByRole("heading", { name: "Billing" })).toBeVisible();
});

test("tenant switch does not leak another tenant data", async ({ page }) => {
  await page.goto("/test-login?tenant=acme");
  await page.goto("/dashboard/other-team/settings");
  await expect(page.getByText("Forbidden")).toBeVisible();
});

Common mistake है सिर्फ login, workspace creation, payment और dashboard दिखना test करना। यह भी test करें कि दूसरा tenant block होता है, member billing change नहीं कर सकता और repeated webhook subscription state खराब नहीं करता।

Launch checklist

Publish या sell करने से पहले कम से कम ये checks करें।

  • Metadata, hero image, official links, internal links और CTA मौजूद हैं
  • .env.example में names हैं, real secrets नहीं
  • हर business update server-side tenant membership check करता है
  • OWNER, ADMIN, BILLING, MEMBER और VIEWER differences test हुए हैं
  • Stripe Webhook signature verification और idempotency मौजूद है
  • Email failures के लिए retry या manual resend path है
  • Admin screen और audit logs production में काम करते हैं
  • Terms, privacy policy, refund policy, tax settings और data retention human review से गुजरे हैं
  • README में local setup, seed, tests और deployment steps हैं

Product की तरह package करें

अगर आप SaaS boilerplate बेचना चाहते हैं, तो code value का सिर्फ एक हिस्सा है। Buyers को setup docs, environment variable map, Stripe configuration, seed data, acceptance tests और support boundary भी चाहिए।

PackageContentsBest fit
Free checklistCLAUDE.md sample, env map, release checklistIdea validate करने वाले solo builders
Starter templateNext.js, Prisma, Auth.js, Stripe, Resend, testsWeekend में MVP बनाने वाले
Pro templateAdmin, audit logs, usage billing, invites, docsPaid SaaS seriously launch करने वाले
Team rolloutRepo review, Claude Code training, review rulesClaude Code adopt करने वाली teams

ClaudeCodeLab में free cheatsheet, Claude Code products और templates और training या implementation consulting उपलब्ध हैं। Free material से workflow validate करें, फिर real repository में fit करने के लिए templates या consulting चुनें।

Practical result

Masa ने एक छोटे validation repository में पहले बहुत broad prompt दिया: “dashboard बना दो”। Screen चल गई, लेकिन दूसरे tenant URL पर direct navigation block नहीं हुआ और Stripe Webhook में signature verification missing था। जब tenant boundaries, roles, audit logs और acceptance tests को CLAUDE.md में लिखा गया, Claude Code ने छोटे diffs बनाए और review checklist साफ हुई। Lesson सीधा है: SaaS boilerplate की value feature count में नहीं, boundaries, tests और operating docs में होती है।

#Claude Code #SaaS #boilerplate #Next.js #Prisma #Stripe
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.