mcpField
S
saas-scaffolder

saas-scaffolder

github.com/alirezarezvani开发28.9k632 次安装2 天前更新
代码仓库

关于 saas-scaffolder

面向产品团队的 SaaS 项目脚手架技能,指导分阶段构建 Next.js 和 TypeScript 应用。它支持选择认证、数据库和支付方案,并在各阶段提供验证检查。

主要功能

  1. 01生成 SaaS 项目结构
  2. 02配置 Next.js、TypeScript 和 App Router
  3. 03选择认证与数据库方案
  4. 04设置支付路由和 Webhook
  5. 05通过构建及行为检查验证各阶段

适用场景

  1. 01根据产品描述启动 SaaS 应用
  2. 02构建认证功能和受保护的仪表板
  3. 03添加订阅结账与账单页面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/alirezarezvani/claude-skills --skill saas-scaffolder

安装后在 Claude Code 中输入/saas-scaffolder确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
architecture-patterns.md
auth-billing-guide.md
saas-architecture-patterns.md
tech-stack-comparison.md
scripts/
project_bootstrapper.py

详细介绍

SaaS Scaffolder

Tier: POWERFUL
Category: Product Team
Domain: Full-Stack Development / Project Bootstrapping


Input Format

Product: [name]
Description: [1-3 sentences]
Auth: nextauth | clerk | supabase
Database: neondb | supabase | planetscale
Payments: stripe | lemonsqueezy | none
Features: [comma-separated list]

File Tree Output

my-saas/
├── app/
│   ├── (auth)/
│   │   ├── login/page.tsx
│   │   ├── register/page.tsx
│   │   └── layout.tsx
│   ├── (dashboard)/
│   │   ├── dashboard/page.tsx
│   │   ├── settings/page.tsx
│   │   ├── billing/page.tsx
│   │   └── layout.tsx
│   ├── (marketing)/
│   │   ├── page.tsx
│   │   ├── pricing/page.tsx
│   │   └── layout.tsx
│   ├── api/
│   │   ├── auth/[...nextauth]/route.ts
│   │   ├── webhooks/stripe/route.ts
│   │   ├── billing/checkout/route.ts
│   │   └── billing/portal/route.ts
│   └── layout.tsx
├── components/
│   ├── ui/
│   ├── auth/
│   │   ├── login-form.tsx
│   │   └── register-form.tsx
│   ├── dashboard/
│   │   ├── sidebar.tsx
│   │   ├── header.tsx
│   │   └── stats-card.tsx
│   ├── marketing/
│   │   ├── hero.tsx
│   │   ├── features.tsx
│   │   ├── pricing.tsx
│   │   └── footer.tsx
│   └── billing/
│       ├── plan-card.tsx
│       └── usage-meter.tsx
├── lib/
│   ├── auth.ts
│   ├── db.ts
│   ├── stripe.ts
│   ├── validations.ts
│   └── utils.ts
├── db/
│   ├── schema.ts
│   └── migrations/
├── hooks/
│   ├── use-subscription.ts
│   └── use-user.ts
├── types/index.ts
├── middleware.ts
├── .env.example
├── drizzle.config.ts
└── next.config.ts

Key Component Patterns

Auth Config (NextAuth)
// lib/auth.ts
import { NextAuthOptions } from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "./db"

export const authOptions: NextAuthOptions = {
  adapter: DrizzleAdapter(db),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    session: async ({ session, user }) => ({
      ...session,
      user: {
        ...session.user,
        id: user.id,
        subscriptionStatus: user.subscriptionStatus,
      },
    }),
  },
  pages: { signIn: "/login" },
}
Database Schema (Drizzle + NeonDB)
// db/schema.ts
import { pgTable, text, timestamp, integer } from "drizzle-orm/pg-core"

export const users = pgTable("users", {
  id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
  name: text("name"),
  email: text("email").notNull().unique(),
  emailVerified: timestamp("emailVerified"),
  image: text("image"),
  stripeCustomerId: text("stripe_customer_id").unique(),
  stripeSubscriptionId: text("stripe_subscription_id"),
  stripePriceId: text("stripe_price_id"),
  stripeCurrentPeriodEnd: timestamp("stripe_current_period_end"),
  createdAt: timestamp("created_at").defaultNow().notNull(),
})

export const accounts = pgTable("accounts", {
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  type: text("type").notNull(),
  provider: text("provider").notNull(),
  providerAccountId: text("provider_account_id").notNull(),
  refresh_token: text("refresh_token"),
  access_token: text("access_token"),
  expires_at: integer("expires_at"),
})
Stripe Checkout Route
// app/api/billing/checkout/route.ts
import { NextResponse } from "next/server"
import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"
import { stripe } from "@/lib/stripe"
import { db } from "@/lib/db"
import { users } from "@/db/schema"
import { eq } from "drizzle-orm"

export async function POST(req: Request) {
  const session = await getServerSession(authOptions)
  if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })

  const { priceId } = await req.json()
  const [user] = await db.select().from(users).where(eq(users.id, session.user.id))

  let customerId = user.stripeCustomerId
  if (!customerId) {
    const customer = await stripe.customers.create({ email: session.user.email! })
    customerId = customer.id
    await db.update(users).set({ stripeCustomerId: customerId }).where(eq(users.id, user.id))
  }

  const checkoutSession = await stripe.checkout.sessions.create({
    customer: customerId,
    mode: "subscription",
    payment_method_types: ["card"],
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?upgraded=true`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
    subscription_data: { trial_period_days: 14 },
  })

  return NextResponse.json({ url: checkoutSession.url })
}
Middleware
// middleware.ts
import { withAuth } from "next-auth/middleware"
import { NextResponse } from "next/server"

export default withAuth(
  function middleware(req) {
    const token = req.nextauth.token
    if (req.nextUrl.pathname.startsWith("/dashboard") && !token) {
      return NextResponse.redirect(new URL("/login", req.url))
    }
  },
  { callbacks: { authorized: ({ token }) => !!token } }
)

export const config = {
  matcher: ["/dashboard/:path*", "/settings/:path*", "/billing/:path*"],
}
Environment Variables Template
# .env.example
NEXT_PUBLIC_APP_URL=http://localhost:3000
DATABASE_URL=postgresql://user:[email protected]/neondb?sslmode=require
NEXTAUTH_SECRET=generate-with-openssl-rand-base64-32
NEXTAUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_PRO_PRICE_ID=price_...

Scaffold Checklist

以下各阶段必须按顺序完成。每个阶段结束时先进行验证,再继续下一阶段。

Phase 1 — Foundation
  • 1. 使用 TypeScript 和 App Router 初始化 Next.js
  • 2. 配置带有自定义主题令牌的 Tailwind CSS
  • 3. 安装并配置 shadcn/ui
  • 4. 配置 ESLint + Prettier
  • 5. 创建包含所有必需变量的 .env.example

✅ Validate: 运行 npm run build,不应出现 TypeScript 或 lint 错误。
🔧 If build fails: 检查 tsconfig.json 的 paths 配置,并确认已安装所有 shadcn/ui 的 peer dependencies。

Phase 2 — Database
  • 6. 安装并配置 Drizzle ORM
  • 7. 编写 Schema(users、accounts、sessions、verification_tokens)
  • 8. 生成并应用初始迁移
  • 9. 从 lib/db.ts 导出 DB 客户端单例
  • 10. 在本地环境中测试数据库连接

✅ Validate: 在测试脚本中运行一个简单的 db.select().from(users),它应返回空数组且不抛出异常。
🔧 If DB connection fails: 确认 NeonDB/Supabase 的 DATABASE_URL 格式包含 ?sslmode=require。检查迁移是否已通过 drizzle-kit push(开发环境)或 drizzle-kit migrate(生产环境)应用。

Phase 3 — Authentication
  • 11. 安装认证提供方(NextAuth / Clerk / Supabase)
  • 12. 配置 OAuth 提供方(Google / GitHub)
  • 13. 创建认证 API 路由
  • 14. Session 回调中添加用户 ID 和订阅状态
  • 15. Middleware 保护仪表盘路由
  • 16. 构建带错误状态的登录与注册页面

✅ Validate: 通过 OAuth 登录,确认 session 用户包含 id 和 subscriptionStatus。在没有 session 的情况下访问 /dashboard,应被重定向到 /login。
🔧 If sign-out loops occur in production: 确保 NEXTAUTH_SECRET 已设置且在各次部署中保持一致。如果出现 TypeScript 错误,添加 declare module "next-auth" 以扩展 session 类型。

Phase 4 — Payments
  • 17. 初始化带有 TypeScript 类型的 Stripe 客户端
  • 18. 创建 Checkout session 路由
  • 19. 创建客户门户路由
  • 20. 编写带签名校验的 Stripe webhook 处理程序
  • 21. Webhook 以幂等方式更新数据库中的用户订阅状态

✅ Validate: 使用 4242 4242 4242 4242 测试卡完成一次 Stripe 测试结账。确认 stripeSubscriptionId 已写入数据库。重放 checkout.session.completed webhook 事件,确认具有幂等性(不会产生重复的数据库写入)。
🔧 If webhook signature fails: 本地使用 stripe listen --forward-to localhost:3000/api/webhooks/stripe,切勿硬编码原始 webhook 密钥。确认 STRIPE_WEBHOOK_SECRET 与监听器输出一致。

Phase 5 — UI
  • 22. 包含 hero、功能与定价板块的落地页
  • 23. 带侧边栏和响应式头部的仪表盘布局
  • 24. 显示当前套餐和升级选项的账单页面
  • 25. 包含个人资料更新表单和成功状态的设置页面

✅ Validate: 运行 npm run build 进行最终生产构建检查。手动浏览所有路由,确认没有布局错乱、session 数据缺失或 hydration 错误。


参考文件

如需更多指导,请在生成脚手架的同时生成以下配套参考文件:

  • CUSTOMIZATION.md:认证提供方、数据库选项、ORM 替代方案、支付提供方、UI 主题,以及计费模式(按席位、固定费率、按用量计费)。
  • PITFALLS.md:常见故障模式,包括缺少 NEXTAUTH_SECRET、Webhook 密钥不匹配、Edge 运行时与 Drizzle 的冲突、未扩展的 session 类型,以及开发环境与生产环境在迁移策略上的差异。
  • BEST_PRACTICES.md:Stripe 单例模式、使用 Server Actions 处理表单变更、幂等的 Webhook 处理器、为异步仪表盘数据设置 Suspense 边界、通过 stripeCurrentPeriodEnd 实现服务端功能开关,以及使用 Upstash Redis 与 @upstash/ratelimit 对认证路由进行速率限制。

评论

支持 Markdown