Token导航 LogoToken导航TokenDH.com
前端设计执行命令github未标认证来源可访问许可证需确认审计提醒

saas-scaffolderSAAS 脚手架

Agent Skill

saas-scaffolder 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

1,623

周安装

69

GitHub Stars

103

下载量

569
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:saas-scaffolder(SAAS 脚手架)
来源仓库:https://github.com/borghei/claude-skills
仓库路径:skills/saas-scaffolder
安装命令:
npx skills add https://github.com/borghei/claude-skills --skill saas-scaffolder
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/borghei/claude-skills --skill saas-scaffolder

简介

saas-scaffolder 快速生成含认证、计费与多租户的完整 SaaS 应用骨架。

  • 集成主流技术栈如 Next.js、Stripe 与 shadcn/ui,缩短启动周期。
  • 产出可直接部署的生产级代码结构,支持后续功能扩展。
  • 首次使用前应审查第三方服务条款与密钥管理机制。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

SaaS Scaffolder

Tier: POWERFUL Category: Engineering / Full-Stack Maintainer: Claude Skills Team

Overview

Generate a complete, production-ready SaaS application boilerplate including authentication (NextAuth, Clerk, or Supabase Auth), database schemas with multi-tenancy, billing integration (Stripe or Lemon Squeezy), API routes with validation, dashboard UI with shadcn/ui, and deployment configuration. Produces a working application from a product specification in under 30 minutes.

Keywords

SaaS, boilerplate, scaffolding, Next.js, authentication, Stripe, billing, multi-tenancy, subscription, starter template, NextAuth, Drizzle ORM, shadcn/ui

Input Specification

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

Generated File Tree

my-saas/
├── app/
│   ├── (auth)/
│   │   ├── login/page.tsx
│   │   ├── register/page.tsx
│   │   ├── forgot-password/page.tsx
│   │   └── layout.tsx
│   ├── (dashboard)/
│   │   ├── dashboard/page.tsx
│   │   ├── settings/
│   │   │   ├── page.tsx              # Profile settings
│   │   │   ├── billing/page.tsx      # Subscription management
│   │   │   └── team/page.tsx         # Team/workspace settings
│   │   └── layout.tsx                # Dashboard shell (sidebar + header)
│   ├── (marketing)/
│   │   ├── page.tsx                  # Landing page
│   │   ├── pricing/page.tsx          # Pricing tiers
│   │   └── layout.tsx
│   ├── api/
│   │   ├── auth/[...nextauth]/route.ts
│   │   ├── webhooks/stripe/route.ts
│   │   ├── billing/
│   │   │   ├── checkout/route.ts
│   │   │   └── portal/route.ts
│   │   └── health/route.ts
│   ├── layout.tsx                    # Root layout
│   └── not-found.tsx
├── components/
│   ├── ui/                           # shadcn/ui components
│   ├── auth/
│   │   ├── login-form.tsx
│   │   └── register-form.tsx
│   ├── dashboard/
│   │   ├── sidebar.tsx
│   │   ├── header.tsx
│   │   └── stats-card.tsx
│   ├── marketing/
│   │   ├── hero.tsx
│   │   ├── features.tsx
│   │   ├── pricing-card.tsx
│   │   └── footer.tsx
│   └── billing/
│       ├── plan-card.tsx
│       └── usage-meter.tsx
├── lib/
│   ├── auth.ts                       # Auth configuration
│   ├── db.ts                         # Database client singleton
│   ├── stripe.ts                     # Stripe client
│   ├── validations.ts                # Zod schemas
│   └── utils.ts                      # Shared utilities
├── db/
│   ├── schema.ts                     # Drizzle schema
│   ├── migrations/                   # Generated migrations
│   └── seed.ts                       # Development seed data
├── hooks/
│   ├── use-subscription.ts
│   └── use-current-user.ts
├── types/
│   └── index.ts                      # Shared TypeScript types
├── middleware.ts                      # Auth + rate limiting
├── .env.example
├── drizzle.config.ts
├── tailwind.config.ts
└── next.config.ts

Database Schema (Multi-Tenant)

// db/schema.ts
import { pgTable, text, timestamp, integer, boolean, uniqueIndex, index } from 'drizzle-orm/pg-core'
import { createId } from '@paralleldrive/cuid2'

// ──── WORKSPACES (Tenancy boundary) ────
export const workspaces = pgTable('workspaces', {
  id: text('id').primaryKey().$defaultFn(createId),
  name: text('name').notNull(),
  slug: text('slug').notNull(),
  plan: text('plan').notNull().default('free'),  // free | pro | enterprise
  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', { withTimezone: true }).defaultNow().notNull(),
  updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
}, (t) => [
  uniqueIndex('workspaces_slug_idx').on(t.slug),
])

// ──── USERS ────
export const users = pgTable('users', {
  id: text('id').primaryKey().$defaultFn(createId),
  email: text('email').notNull().unique(),
  name: text('name'),
  avatarUrl: text('avatar_url'),
  emailVerified: timestamp('email_verified', { withTimezone: true }),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
})

// ──── WORKSPACE MEMBERS ────
export const workspaceMembers = pgTable('workspace_members', {
  id: text('id').primaryKey().$defaultFn(createId),
  workspaceId: text('workspace_id').notNull().references(() => workspaces.id, { onDelete: 'cascade' }),
  userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
  role: text('role').notNull().default('member'), // owner | admin | member
  joinedAt: timestamp('joined_at', { withTimezone: true }).defaultNow().notNull(),
}, (t) => [
  uniqueIndex('workspace_members_unique').on(t.workspaceId, t.userId),
  index('workspace_members_workspace_idx').on(t.workspaceId),
])

// ──── ACCOUNTS (OAuth) ────
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(),
  refreshToken: text('refresh_token'),
  accessToken: text('access_token'),
  expiresAt: integer('expires_at'),
})

// ──── SESSIONS ────
export const sessions = pgTable('sessions', {
  sessionToken: text('session_token').primaryKey(),
  userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
  expires: timestamp('expires', { withTimezone: true }).notNull(),
})

Authentication Configuration

// lib/auth.ts
import { DrizzleAdapter } from '@auth/drizzle-adapter'
import NextAuth from 'next-auth'
import Google from 'next-auth/providers/google'
import GitHub from 'next-auth/providers/github'
import Resend from 'next-auth/providers/resend'
import { db } from './db'

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: DrizzleAdapter(db),
  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    GitHub({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
    Resend({
      from: 'noreply@myapp.com',
    }),
  ],
  callbacks: {
    session: async ({ session, user }) => ({
      ...session,
      user: {
        ...session.user,
        id: user.id,
      },
    }),
  },
  pages: {
    signIn: '/login',
    error: '/login',
  },
})

Stripe Billing Integration

Checkout Session

// app/api/billing/checkout/route.ts
import { NextResponse } from 'next/server'
import { auth } from '@/lib/auth'
import { stripe } from '@/lib/stripe'
import { db } from '@/lib/db'
import { workspaces } from '@/db/schema'
import { eq } from 'drizzle-orm'

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

  const { priceId, workspaceId } = await req.json()

  // Get or create Stripe customer
  const [workspace] = await db.select().from(workspaces).where(eq(workspaces.id, workspaceId))
  if (!workspace) {
    return NextResponse.json({ error: 'Workspace not found' }, { status: 404 })
  }

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

  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}/settings/billing?success=true`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
    subscription_data: { trial_period_days: 14 },
    metadata: { workspaceId },
  })

  return NextResponse.json({ url: checkoutSession.url })
}

Webhook Handler

// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers'
import { stripe } from '@/lib/stripe'
import { db } from '@/lib/db'
import { workspaces } from '@/db/schema'
import { eq } from 'drizzle-orm'

export async function POST(req: Request) {
  const body = await req.text()
  const signature = (await headers()).get('Stripe-Signature')!

  let event
  try {
    event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!)
  } catch (err) {
    return new Response(`Webhook Error: ${err.message}`, { status: 400 })
  }

  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object
      const subscription = await stripe.subscriptions.retrieve(session.subscription as string)
      await db.update(workspaces).set({
        stripeSubscriptionId: subscription.id,
        stripePriceId: subscription.items.data[0].price.id,
        stripeCurrentPeriodEnd: new Date(subscription.current_period_end * 1000),
      }).where(eq(workspaces.stripeCustomerId, session.customer as string))
      break
    }

    case 'invoice.payment_succeeded': {
      const invoice = event.data.object
      const subscription = await stripe.subscriptions.retrieve(invoice.subscription as string)
      await db.update(workspaces).set({
        stripeCurrentPeriodEnd: new Date(subscription.current_period_end * 1000),
      }).where(eq(workspaces.stripeCustomerId, invoice.customer as string))
      break
    }

    case 'customer.subscription.deleted': {
      const subscription = event.data.object
      await db.update(workspaces).set({
        plan: 'free',
        stripeSubscriptionId: null,
        stripePriceId: null,
        stripeCurrentPeriodEnd: null,
      }).where(eq(workspaces.stripeCustomerId, subscription.customer as string))
      break
    }
  }

  return new Response('OK', { status: 200 })
}

Middleware (Auth + Rate Limiting)

// middleware.ts
import { auth } from '@/lib/auth'
import { NextResponse } from 'next/server'

export default auth((req) => {
  const { pathname } = req.nextUrl
  const isAuthenticated = !!req.auth

  // Protected routes
  if (pathname.startsWith('/dashboard') || pathname.startsWith('/settings')) {
    if (!isAuthenticated) {
      return NextResponse.redirect(new URL('/login', req.url))
    }
  }

  // Redirect logged-in users away from auth pages
  if ((pathname === '/login' || pathname === '/register') && isAuthenticated) {
    return NextResponse.redirect(new URL('/dashboard', req.url))
  }

  return NextResponse.next()
})

export const config = {
  matcher: ['/dashboard/:path*', '/settings/:path*', '/login', '/register'],
}

Environment Variables

# .env.example

# ─── App ───
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXTAUTH_SECRET=           # openssl rand -base64 32
NEXTAUTH_URL=http://localhost:3000

# ─── Database ───
DATABASE_URL=              # postgresql://user:pass@host/db?sslmode=require

# ─── OAuth Providers ───
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

# ─── Stripe ───
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_PRO_MONTHLY_PRICE_ID=price_...
STRIPE_PRO_YEARLY_PRICE_ID=price_...

# ─── Email ───
RESEND_API_KEY=re_...

# ─── Monitoring (optional) ───
SENTRY_DSN=

Scaffolding Phases

Execute these phases in order. Validate at the end of each phase.

Phase 1: Foundation

  1. Initialize Next.js with TypeScript and App Router
  2. Configure Tailwind CSS with custom theme
  3. Install and configure shadcn/ui
  4. Set up ESLint and Prettier
  5. Create .env.example

Validate: pnpm build completes without errors.

Phase 2: Database

  1. Install and configure Drizzle ORM
  2. Write schema (users, accounts, sessions, workspaces, members)
  3. Generate and apply initial migration
  4. Export DB client singleton from lib/db.ts
  5. Create seed script with test data

Validate: pnpm db:push succeeds and pnpm db:seed creates test data.

Phase 3: Authentication

  1. Install and configure NextAuth v5 with Drizzle adapter
  2. Set up OAuth providers (Google, GitHub)
  3. Create auth API route
  4. Implement middleware for route protection
  5. Build login and register pages

Validate: OAuth login works, session persists, protected routes redirect.

Phase 4: Billing

  1. Initialize Stripe client
  2. Create checkout session API route
  3. Create customer portal API route
  4. Implement webhook handler with signature verification
  5. Build pricing page and billing settings page

Validate: Complete a test checkout with card 4242 4242 4242 4242. Verify subscription data written to DB. Replay webhook event and confirm idempotency.

Phase 5: UI and Polish

  1. Build landing page (hero, features, pricing, footer)
  2. Build dashboard layout (sidebar, header, stats)
  3. Build settings pages (profile, billing, team)
  4. Add loading states, error boundaries, and not-found pages
  5. Configure deployment (Vercel/Railway)

Validate: pnpm build succeeds. All routes render correctly. No hydration errors.

Multi-Tenancy Patterns

Workspace-Scoped Queries

// Every data query must be scoped to the current workspace
export async function getProjects(workspaceId: string) {
  return db.query.projects.findMany({
    where: eq(projects.workspaceId, workspaceId),
    orderBy: [desc(projects.updatedAt)],
  })
}

// Middleware: resolve workspace from URL or session
export function getCurrentWorkspace(req: Request) {
  // Option A: workspace slug in URL (/workspace/acme/dashboard)
  // Option B: workspace ID in session/cookie
  // Option C: header (X-Workspace-Id) for API calls
}

Plan-Based Feature Gating

export function canAccessFeature(workspace: Workspace, feature: string): boolean {
  const PLAN_FEATURES: Record<string, string[]> = {
    free: ['basic_dashboard', 'up_to_3_members'],
    pro: ['advanced_analytics', 'up_to_20_members', 'custom_domain', 'api_access'],
    enterprise: ['sso', 'unlimited_members', 'audit_log', 'sla'],
  }

  const isActive = workspace.stripeCurrentPeriodEnd
    ? workspace.stripeCurrentPeriodEnd > new Date()
    : workspace.plan === 'free'

  if (!isActive) return PLAN_FEATURES.free.includes(feature)
  return PLAN_FEATURES[workspace.plan]?.includes(feature) ?? false
}

Common Pitfalls

  • Missing NEXTAUTH_SECRET in production — causes session errors; generate with openssl rand -base64 32
  • Webhook signature verification skipped — always verify Stripe webhook signatures; test with stripe listen
  • **workspace:* in session but not refreshed** — stale subscription data; recheck on billing pages
  • Edge Runtime conflicts with Drizzle — Drizzle needs Node.js runtime; set export const runtime = 'nodejs' on API routes
  • No idempotent webhook handling — Stripe may send duplicate events; use event.id for deduplication
  • Hardcoded Stripe price IDs — store in env vars, not in code; prices change between test and live mode

Best Practices

  1. Stripe singleton — create the client once in lib/stripe.ts, import everywhere
  2. Server actions for form mutations — use Next.js Server Actions instead of API routes for forms
  3. Idempotent webhook handlers — check if the event was already processed before writing to DB
  4. Suspense boundaries for async data — wrap dashboard data in <Suspense> with loading skeletons
  5. Feature gating at the server level — check stripeCurrentPeriodEnd on the server, not the client
  6. Rate limiting on auth routes — prevent brute force with Upstash Redis + @upstash/ratelimit
  7. Workspace context in every query — never query without scoping to the current workspace
  8. Test with Stripe CLIstripe listen --forward-to localhost:3000/api/webhooks/stripe for local development

Troubleshooting

ProblemCauseSolution
NEXTAUTH_URL mismatch errors in productionEnvironment variable not updated from localhost defaultSet NEXTAUTH_URL to your actual production domain; omit trailing slash
Stripe webhook returns 400 on every eventRaw body is consumed before signature verificationEnsure the webhook route uses req.text() before any JSON parsing; do not use body-parser middleware on the webhook endpoint
Drizzle migrations fail with "relation already exists"Migration was partially applied or schema drifted from migration historyRun pnpm drizzle-kit drop to reset the migration journal, then regenerate with pnpm drizzle-kit generate and reapply
OAuth callback redirects to wrong URLRedirect URI registered in provider console does not match NEXTAUTH_URLUpdate the authorized redirect URI in Google/GitHub developer console to match your deployment URL exactly
Multi-tenant queries return data from other workspacesMissing workspaceId filter in a database queryAudit all db.query and db.select calls to ensure every query includes a where clause scoped to the current workspace
Hydration mismatch on dashboard pagesServer-rendered HTML differs from client due to conditional auth checksMove auth-dependent rendering into client components or wrap with <Suspense>; avoid reading session in server components that also render on the client
Stripe test mode charges succeed but live mode failsLive mode price IDs differ from test mode IDsUse separate environment variables for test vs. live Stripe keys and price IDs; verify .env.production references the correct live values

Success Criteria

  • Scaffolded project passes pnpm build with zero errors and zero TypeScript warnings on first run
  • End-to-end authentication flow (register, login, logout, password reset) completes in under 60 seconds of manual testing
  • Stripe checkout creates a subscription and webhook handler updates the database within 5 seconds of payment completion
  • Multi-tenant data isolation verified: queries scoped to Workspace A return zero rows belonging to Workspace B
  • Lighthouse performance score on the landing page is 90+ on mobile with no accessibility violations at the AA level
  • Time from git clone to running local dev server with seeded data is under 10 minutes following the generated README
  • All environment variables are documented in .env.example with descriptions, and the app fails fast with clear error messages when required variables are missing

Scope & Limitations

This skill covers:

  • Full-stack SaaS scaffolding with Next.js App Router, TypeScript, Tailwind, and shadcn/ui
  • Authentication setup with NextAuth v5, Clerk, or Supabase Auth including OAuth and magic link providers
  • Stripe and Lemon Squeezy billing integration with checkout, webhooks, and customer portal
  • Multi-tenancy patterns (workspace/organization) with role-based access and plan-based feature gating

This skill does NOT cover:

  • Ongoing Stripe billing logic beyond initial integration (metered billing, usage-based pricing, invoicing customization) — see stripe-integration-expert
  • Database schema design decisions beyond the core tenancy model (complex relational modeling, indexing strategies) — see database-schema-designer
  • CI/CD pipeline configuration, deployment automation, or infrastructure provisioning — see ci-cd-pipeline-builder
  • API design standards, versioning, or OpenAPI specification generation — see api-design-reviewer

Integration Points

SkillIntegrationData Flow
stripe-integration-expertExtends the scaffolded Stripe setup with advanced billing patterns (metered, tiered, usage-based)Scaffolder outputs base Stripe config and webhook handler; Stripe expert refines pricing models and adds invoice customization
database-schema-designerDesigns extended schemas beyond the core tenancy tablesScaffolder provides baseline users/workspaces/members schema; schema designer adds domain-specific entities and optimizes indexes
api-design-reviewerReviews and improves the generated API routes for consistency and standards complianceScaffolder generates initial API routes; reviewer audits naming, error handling, and response formats
ci-cd-pipeline-builderCreates deployment pipelines for the scaffolded projectScaffolder outputs the application code; pipeline builder adds GitHub Actions, preview deployments, and production release workflows
env-secrets-managerAudits and secures the environment variable configurationScaffolder generates .env.example; secrets manager validates no secrets are hardcoded and recommends vault integration
observability-designerAdds logging, tracing, and monitoring to the scaffolded applicationScaffolder provides the application structure; observability designer instruments API routes, webhooks, and auth flows

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

35.02%
按下载量换算199

Claude

28.94%
按下载量换算165

Cursor

19.96%
按下载量换算114

Gemini CLI

9.62%
按下载量换算55

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

执行命令

安装流程涉及命令执行,可能通过 npx skills add https://github.com/borghei/claude-skills --skill saas-scaffolder 联网下载 Skill 或依赖。用户安装前应确认命令来源、仓库内容和执行环境。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills