[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-e7504ad8-179a-4439-b6e2-e69ef2f970b9":3,"$f0FSjZ4gglduZIzm4LW0ifeFi-Uvr74VpLDayHDqsk70":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"e7504ad8-179a-4439-b6e2-e69ef2f970b9","saas-scaffolder","生成完整的、可直接投入生产的SaaS项目模板，包括身份验证、数据库模式、计费集成、API路由和基于Next.js 14+ App Router、TypeScript、Tailwind CSS、shadcn\u002Fui、Drizzle ORM和Stripe的工作仪表板。当用户想要创建新的SaaS应用、启动基于订阅的Web项目、构建Next.js应用程序或提及类似启动模板、模板、新项目或设置身份验证和支付等术语时使用。","cat_coding_frontend","mod_coding","alirezarezvani,coding","---\nname: \"saas-scaffolder\"\ndescription: \"Generates complete, production-ready SaaS project boilerplate including authentication, database schemas, billing integration, API routes, and a working dashboard using Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn\u002Fui, Drizzle ORM, and Stripe. Use when the user wants to create a new SaaS app, start a subscription-based web project, scaffold a Next.js application, or mentions terms like starter template, boilerplate, new project, or wiring up auth and payments.\"\n---\n\n# SaaS Scaffolder\n\n**Tier:** POWERFUL  \n**Category:** Product Team  \n**Domain:** Full-Stack Development \u002F Project Bootstrapping\n\n---\n\n## Input Format\n\n```\nProduct: [name]\nDescription: [1-3 sentences]\nAuth: nextauth | clerk | supabase\nDatabase: neondb | supabase | planetscale\nPayments: stripe | lemonsqueezy | none\nFeatures: [comma-separated list]\n```\n\n---\n\n## File Tree Output\n\n```\nmy-saas\u002F\n├── app\u002F\n│   ├── (auth)\u002F\n│   │   ├── login\u002Fpage.tsx\n│   │   ├── register\u002Fpage.tsx\n│   │   └── layout.tsx\n│   ├── (dashboard)\u002F\n│   │   ├── dashboard\u002Fpage.tsx\n│   │   ├── settings\u002Fpage.tsx\n│   │   ├── billing\u002Fpage.tsx\n│   │   └── layout.tsx\n│   ├── (marketing)\u002F\n│   │   ├── page.tsx\n│   │   ├── pricing\u002Fpage.tsx\n│   │   └── layout.tsx\n│   ├── api\u002F\n│   │   ├── auth\u002F[...nextauth]\u002Froute.ts\n│   │   ├── webhooks\u002Fstripe\u002Froute.ts\n│   │   ├── billing\u002Fcheckout\u002Froute.ts\n│   │   └── billing\u002Fportal\u002Froute.ts\n│   └── layout.tsx\n├── components\u002F\n│   ├── ui\u002F\n│   ├── auth\u002F\n│   │   ├── login-form.tsx\n│   │   └── register-form.tsx\n│   ├── dashboard\u002F\n│   │   ├── sidebar.tsx\n│   │   ├── header.tsx\n│   │   └── stats-card.tsx\n│   ├── marketing\u002F\n│   │   ├── hero.tsx\n│   │   ├── features.tsx\n│   │   ├── pricing.tsx\n│   │   └── footer.tsx\n│   └── billing\u002F\n│       ├── plan-card.tsx\n│       └── usage-meter.tsx\n├── lib\u002F\n│   ├── auth.ts\n│   ├── db.ts\n│   ├── stripe.ts\n│   ├── validations.ts\n│   └── utils.ts\n├── db\u002F\n│   ├── schema.ts\n│   └── migrations\u002F\n├── hooks\u002F\n│   ├── use-subscription.ts\n│   └── use-user.ts\n├── types\u002Findex.ts\n├── middleware.ts\n├── .env.example\n├── drizzle.config.ts\n└── next.config.ts\n```\n\n---\n\n## Key Component Patterns\n\n### Auth Config (NextAuth)\n\n```typescript\n\u002F\u002F lib\u002Fauth.ts\nimport { NextAuthOptions } from \"next-auth\"\nimport GoogleProvider from \"next-auth\u002Fproviders\u002Fgoogle\"\nimport { DrizzleAdapter } from \"@auth\u002Fdrizzle-adapter\"\nimport { db } from \".\u002Fdb\"\n\nexport const authOptions: NextAuthOptions = {\n  adapter: DrizzleAdapter(db),\n  providers: [\n    GoogleProvider({\n      clientId: process.env.GOOGLE_CLIENT_ID!,\n      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n    }),\n  ],\n  callbacks: {\n    session: async ({ session, user }) => ({\n      ...session,\n      user: {\n        ...session.user,\n        id: user.id,\n        subscriptionStatus: user.subscriptionStatus,\n      },\n    }),\n  },\n  pages: { signIn: \"\u002Flogin\" },\n}\n```\n\n### Database Schema (Drizzle + NeonDB)\n\n```typescript\n\u002F\u002F db\u002Fschema.ts\nimport { pgTable, text, timestamp, integer } from \"drizzle-orm\u002Fpg-core\"\n\nexport const users = pgTable(\"users\", {\n  id: text(\"id\").primaryKey().$defaultFn(() => crypto.randomUUID()),\n  name: text(\"name\"),\n  email: text(\"email\").notNull().unique(),\n  emailVerified: timestamp(\"emailVerified\"),\n  image: text(\"image\"),\n  stripeCustomerId: text(\"stripe_customer_id\").unique(),\n  stripeSubscriptionId: text(\"stripe_subscription_id\"),\n  stripePriceId: text(\"stripe_price_id\"),\n  stripeCurrentPeriodEnd: timestamp(\"stripe_current_period_end\"),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n})\n\nexport const accounts = pgTable(\"accounts\", {\n  userId: text(\"user_id\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n  type: text(\"type\").notNull(),\n  provider: text(\"provider\").notNull(),\n  providerAccountId: text(\"provider_account_id\").notNull(),\n  refresh_token: text(\"refresh_token\"),\n  access_token: text(\"access_token\"),\n  expires_at: integer(\"expires_at\"),\n})\n```\n\n### Stripe Checkout Route\n\n```typescript\n\u002F\u002F app\u002Fapi\u002Fbilling\u002Fcheckout\u002Froute.ts\nimport { NextResponse } from \"next\u002Fserver\"\nimport { getServerSession } from \"next-auth\"\nimport { authOptions } from \"@\u002Flib\u002Fauth\"\nimport { stripe } from \"@\u002Flib\u002Fstripe\"\nimport { db } from \"@\u002Flib\u002Fdb\"\nimport { users } from \"@\u002Fdb\u002Fschema\"\nimport { eq } from \"drizzle-orm\"\n\nexport async function POST(req: Request) {\n  const session = await getServerSession(authOptions)\n  if (!session?.user) return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 })\n\n  const { priceId } = await req.json()\n  const [user] = await db.select().from(users).where(eq(users.id, session.user.id))\n\n  let customerId = user.stripeCustomerId\n  if (!customerId) {\n    const customer = await stripe.customers.create({ email: session.user.email! })\n    customerId = customer.id\n    await db.update(users).set({ stripeCustomerId: customerId }).where(eq(users.id, user.id))\n  }\n\n  const checkoutSession = await stripe.checkout.sessions.create({\n    customer: customerId,\n    mode: \"subscription\",\n    payment_method_types: [\"card\"],\n    line_items: [{ price: priceId, quantity: 1 }],\n    success_url: `${process.env.NEXT_PUBLIC_APP_URL}\u002Fdashboard?upgraded=true`,\n    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}\u002Fpricing`,\n    subscription_data: { trial_period_days: 14 },\n  })\n\n  return NextResponse.json({ url: checkoutSession.url })\n}\n```\n\n### Middleware\n\n```typescript\n\u002F\u002F middleware.ts\nimport { withAuth } from \"next-auth\u002Fmiddleware\"\nimport { NextResponse } from \"next\u002Fserver\"\n\nexport default withAuth(\n  function middleware(req) {\n    const token = req.nextauth.token\n    if (req.nextUrl.pathname.startsWith(\"\u002Fdashboard\") && !token) {\n      return NextResponse.redirect(new URL(\"\u002Flogin\", req.url))\n    }\n  },\n  { callbacks: { authorized: ({ token }) => !!token } }\n)\n\nexport const config = {\n  matcher: [\"\u002Fdashboard\u002F:path*\", \"\u002Fsettings\u002F:path*\", \"\u002Fbilling\u002F:path*\"],\n}\n```\n\n### Environment Variables Template\n\n```bash\n# .env.example\nNEXT_PUBLIC_APP_URL=http:\u002F\u002Flocalhost:3000\nDATABASE_URL=postgresql:\u002F\u002Fuser:pass@ep-xxx.us-east-1.aws.neon.tech\u002Fneondb?sslmode=require\nNEXTAUTH_SECRET=generate-with-openssl-rand-base64-32\nNEXTAUTH_URL=http:\u002F\u002Flocalhost:3000\nGOOGLE_CLIENT_ID=\nGOOGLE_CLIENT_SECRET=\nSTRIPE_SECRET_KEY=sk_test_...\nSTRIPE_WEBHOOK_SECRET=whsec_...\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...\nSTRIPE_PRO_PRICE_ID=price_...\n```\n\n---\n\n## Scaffold Checklist\n\nThe following phases must be completed in order. **Validate at the end of each phase before proceeding.**\n\n### Phase 1 — Foundation\n- [ ] 1. Next.js initialized with TypeScript and App Router\n- [ ] 2. Tailwind CSS configured with custom theme tokens\n- [ ] 3. shadcn\u002Fui installed and configured\n- [ ] 4. ESLint + Prettier configured\n- [ ] 5. `.env.example` created with all required variables\n\n✅ **Validate:** Run `npm run build` — no TypeScript or lint errors should appear.  \n🔧 **If build fails:** Check `tsconfig.json` paths and that all shadcn\u002Fui peer dependencies are installed.\n\n### Phase 2 — Database\n- [ ] 6. Drizzle ORM installed and configured\n- [ ] 7. Schema written (users, accounts, sessions, verification_tokens)\n- [ ] 8. Initial migration generated and applied\n- [ ] 9. DB client singleton exported from `lib\u002Fdb.ts`\n- [ ] 10. DB connection tested in local environment\n\n✅ **Validate:** Run a simple `db.select().from(users)` in a test script — it should return an empty array without throwing.  \n🔧 **If DB connection fails:** Verify `DATABASE_URL` format includes `?sslmode=require` for NeonDB\u002FSupabase. Check that the migration has been applied with `drizzle-kit push` (dev) or `drizzle-kit migrate` (prod).\n\n### Phase 3 — Authentication\n- [ ] 11. Auth provider installed (NextAuth \u002F Clerk \u002F Supabase)\n- [ ] 12. OAuth provider configured (Google \u002F GitHub)\n- [ ] 13. Auth API route created\n- [ ] 14. Session callback adds user ID and subscription status\n- [ ] 15. Middleware protects dashboard routes\n- [ ] 16. Login and register pages built with error states\n\n✅ **Validate:** Sign in via OAuth, confirm session user has `id` and `subscriptionStatus`. Attempt to access `\u002Fdashboard` without a session — you should be redirected to `\u002Flogin`.  \n🔧 **If sign-out loops occur in production:** Ensure `NEXTAUTH_SECRET` is set and consistent across deployments. Add `declare module \"next-auth\"` to extend session types if TypeScript errors appear.\n\n### Phase 4 — Payments\n- [ ] 17. Stripe client initialized with TypeScript types\n- [ ] 18. Checkout session route created\n- [ ] 19. Customer portal route created\n- [ ] 20. Stripe webhook handler with signature verification\n- [ ] 21. Webhook updates user subscription status in DB idempotently\n\n✅ **Validate:** Complete a Stripe test checkout using a `4242 4242 4242 4242` card. Confirm `stripeSubscriptionId` is written to the DB. Replay the `checkout.session.completed` webhook event and confirm idempotency (no duplicate DB writes).  \n🔧 **If webhook signature fails:** Use `stripe listen --forward-to localhost:3000\u002Fapi\u002Fwebhooks\u002Fstripe` locally — never hardcode the raw webhook secret. Verify `STRIPE_WEBHOOK_SECRET` matches the listener output.\n\n### Phase 5 — UI\n- [ ] 22. Landing page with hero, features, pricing sections\n- [ ] 23. Dashboard layout with sidebar and responsive header\n- [ ] 24. Billing page showing current plan and upgrade options\n- [ ] 25. Settings page with profile update form and success states\n\n✅ **Validate:** Run `npm run build` for a final production build check. Navigate all routes manually and confirm no broken layouts, missing session data, or hydration errors.\n\n---\n\n## Reference Files\n\nFor additional guidance, generate the following companion reference files alongside the scaffold:\n\n- **`CUSTOMIZATION.md`** — Auth providers, database options, ORM alternatives, payment providers, UI themes, and billing models (per-seat, flat-rate, usage-based).\n- **`PITFALLS.md`** — Common failure modes: missing `NEXTAUTH_SECRET`, webhook secret mismatches, Edge runtime conflicts with Drizzle, unextended session types, and migration strategy differences between dev and prod.\n- **`BEST_PRACTICES.md`** — Stripe singleton pattern, server actions for form mutations, idempotent webhook handlers, `Suspense` boundaries for async dashboard data, server-side feature gating via `stripeCurrentPeriodEnd`, and rate limiting on auth routes with Upstash Redis + `@upstash\u002Fratelimit`.\n","","imported","https:\u002F\u002Fgithub.com\u002Falirezarezvani\u002Fclaude-skills","user_system_seed","SkillOPIC",true,171,1657,"2026-05-16 14:04:02",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"前端开发","frontend","mdi-language-html5","HTML\u002FCSS\u002FJavaScript\u002F框架相关",1,96,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"9b622a59-0fb1-4bf2-b34d-32ecfda40720","1.0.0","saas-scaffolder.zip",20661,"uploads\u002Fskills\u002Fe7504ad8-179a-4439-b6e2-e69ef2f970b9\u002Fsaas-scaffolder.zip","37aa4621c7407893132acd76bde1a25057a269e9da69a961287acc3686717103","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":10805},{\"path\":\"references\u002Farchitecture-patterns.md\",\"isDirectory\":false,\"size\":6727},{\"path\":\"references\u002Fauth-billing-guide.md\",\"isDirectory\":false,\"size\":7143},{\"path\":\"references\u002Fsaas-architecture-patterns.md\",\"isDirectory\":false,\"size\":2764},{\"path\":\"references\u002Ftech-stack-comparison.md\",\"isDirectory\":false,\"size\":9028},{\"path\":\"scripts\u002Fproject_bootstrapper.py\",\"isDirectory\":false,\"size\":14190}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]