SiamCafe.net Blog
Cybersecurity

shadcn/ui SaaS Architecture สร้าง SaaS App ด้วย Next.js และ Modern UI

shadcn ui saas architecture
Shadcn UI SaaS Architecture | SiamCafe Blog
2025-12-12· อ. บอม — SiamCafe.net· 1,154 คำ

shadcn/ui ?????????????????????

shadcn/ui ???????????? component library ?????????????????? React ???????????????????????????????????? library ?????????????????? ???????????????????????? install ???????????? npm package shadcn/ui ????????? copy component source code ???????????? project ?????????????????? ??????????????? customize ????????? 100% ?????????????????? lock-in ????????? library version ????????????????????? Radix UI (headless components) ????????? Tailwind CSS

?????????????????? SaaS Architecture shadcn/ui ??????????????????????????????????????? Fully customizable ???????????? design system ?????????????????? brand ?????????, Tree-shakeable ???????????????????????? components ?????????????????????????????? bundle size ????????????, Accessible Radix UI ????????? accessibility ??????????????????????????????????????????????????? (WCAG 2.1), TypeScript ????????? component ?????? type safety, Dark mode built-in ?????????????????? light/dark theme

SaaS (Software as a Service) applications ????????????????????? UI components ???????????????????????? Dashboard, Data tables, Forms, Charts, Navigation, Settings panels shadcn/ui ??????????????? component ?????????????????????????????? ??????????????? blocks (pre-built layouts) ?????????????????? SaaS ??????????????????????????????????????????

????????????????????? SaaS Project ???????????? shadcn/ui

Setup Next.js SaaS project

# === shadcn/ui SaaS Project Setup ===

# 1. Create Next.js App
npx create-next-app@latest my-saas --typescript --tailwind --eslint --app --src-dir
cd my-saas

# 2. Initialize shadcn/ui
npx shadcn-ui@latest init

# Configuration options:
# Style: Default
# Base color: Slate
# CSS variables: Yes
# Tailwind config: tailwind.config.ts
# Components alias: @/components
# Utils alias: @/lib/utils

# 3. Install Essential SaaS Components
npx shadcn-ui@latest add button card input label
npx shadcn-ui@latest add dialog dropdown-menu avatar
npx shadcn-ui@latest add table tabs badge separator
npx shadcn-ui@latest add form select textarea checkbox
npx shadcn-ui@latest add sheet sidebar navigation-menu
npx shadcn-ui@latest add chart toast sonner
npx shadcn-ui@latest add command popover calendar

# 4. Install Additional Dependencies
npm install next-auth @prisma/client prisma
npm install stripe @stripe/stripe-js
npm install zustand @tanstack/react-query
npm install lucide-react date-fns zod

# 5. Project Structure
cat > project-structure.txt << 'EOF'
src/
  app/
    (auth)/
      login/page.tsx
      register/page.tsx
    (dashboard)/
      layout.tsx
      page.tsx
      settings/page.tsx
      billing/page.tsx
    api/
      auth/[...nextauth]/route.ts
      stripe/webhook/route.ts
  components/
    ui/           # shadcn/ui components
    dashboard/    # Dashboard-specific components
    forms/        # Form components
    layout/       # Layout components
  lib/
    utils.ts
    prisma.ts
    stripe.ts
    auth.ts
  hooks/
    use-subscription.ts
    use-user.ts
EOF

# 6. Tailwind Config for SaaS Theme
cat > tailwind.config.ts << 'EOF'
import type { Config } from "tailwindcss"

const config: Config = {
  darkMode: ["class"],
  content: ["./src/**/*.{ts,tsx}"],
  theme: {
    extend: {
      colors: {
        border: "hsl(var(--border))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
}
export default config
EOF

echo "SaaS project initialized"

??????????????? Dashboard Components

SaaS Dashboard ???????????? shadcn/ui

// === SaaS Dashboard Components ===

// File: src/components/dashboard/stats-cards.tsx
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { ArrowUpRight, ArrowDownRight, Users, DollarSign, Activity, TrendingUp } from "lucide-react"

interface StatCardProps {
  title: string
  value: string
  change: number
  icon: React.ReactNode
}

function StatCard({ title, value, change, icon }: StatCardProps) {
  const isPositive = change >= 0
  return (
    <Card>
      <CardHeader className="flex flex-row items-center justify-between pb-2">
        <CardTitle className="text-sm font-medium text-muted-foreground">
          {title}
        </CardTitle>
        {icon}
      </CardHeader>
      <CardContent>
        <div className="text-2xl font-bold">{value}</div>
        <div className={`flex items-center text-xs `}>
          {isPositive ? <ArrowUpRight className="h-4 w-4" /> : <ArrowDownRight className="h-4 w-4" />}
          {Math.abs(change)}% from last month
        </div>
      </CardContent>
    </Card>
  )
}

export function DashboardStats() {
  return (
    <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
      <StatCard title="Total Revenue" value="$45,231" change={20.1} icon={<DollarSign className="h-4 w-4 text-muted-foreground" />} />
      <StatCard title="Subscriptions" value="2,350" change={10.5} icon={<Users className="h-4 w-4 text-muted-foreground" />} />
      <StatCard title="Active Users" value="12,234" change={-2.3} icon={<Activity className="h-4 w-4 text-muted-foreground" />} />
      <StatCard title="Growth Rate" value="15.2%" change={4.1} icon={<TrendingUp className="h-4 w-4 text-muted-foreground" />} />
    </div>
  )
}

// File: src/components/dashboard/sidebar-nav.tsx
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { LayoutDashboard, Users, Settings, CreditCard, BarChart3, FileText } from "lucide-react"

const navItems = [
  { title: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
  { title: "Customers", href: "/customers", icon: Users },
  { title: "Analytics", href: "/analytics", icon: BarChart3 },
  { title: "Billing", href: "/billing", icon: CreditCard },
  { title: "Documents", href: "/documents", icon: FileText },
  { title: "Settings", href: "/settings", icon: Settings },
]

export function SidebarNav({ currentPath }: { currentPath: string }) {
  return (
    <nav className="flex flex-col gap-1 p-4">
      {navItems.map((item) => (
        <Button key={item.href} variant={currentPath === item.href ? "secondary" : "ghost"} className="justify-start" asChild>
          <a href={item.href}>
            <item.icon className="mr-2 h-4 w-4" />
            {item.title}
          </a>
        </Button>
      ))}
    </nav>
  )
}

Authentication ????????? Multi-Tenancy

???????????? Auth ????????? Multi-Tenant ?????????????????? SaaS

// === Authentication & Multi-Tenancy ===

// File: src/lib/auth.ts
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import CredentialsProvider from "next-auth/providers/credentials"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/lib/prisma"

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    CredentialsProvider({
      name: "credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        // Validate credentials against database
        const user = await prisma.user.findUnique({
          where: { email: credentials?.email as string },
        })
        if (!user) return null
        // Verify password (use bcrypt in production)
        return { id: user.id, email: user.email, name: user.name }
      },
    }),
  ],
  callbacks: {
    async session({ session, token }) {
      if (token.sub) {
        session.user.id = token.sub
        // Add tenant info
        const membership = await prisma.teamMember.findFirst({
          where: { userId: token.sub },
          include: { team: true },
        })
        if (membership) {
          session.user.teamId = membership.team.id
          session.user.role = membership.role
        }
      }
      return session
    },
  },
})

// File: prisma/schema.prisma (Multi-Tenant Schema)
/*
model Team {
  id        String   @id @default(cuid())
  name      String
  slug      String   @unique
  plan      String   @default("free") // free, pro, enterprise
  members   TeamMember[]
  createdAt DateTime @default(now())
}

model TeamMember {
  id     String @id @default(cuid())
  role   String @default("member") // owner, admin, member
  user   User   @relation(fields: [userId], references: [id])
  userId String
  team   Team   @relation(fields: [teamId], references: [id])
  teamId String
  @@unique([userId, teamId])
}

model User {
  id       String       @id @default(cuid())
  email    String       @unique
  name     String?
  teams    TeamMember[]
}
*/

Billing ????????? Subscription

Stripe integration ?????????????????? SaaS billing

# === Stripe Billing Integration ===

# 1. Stripe Configuration
cat > src/lib/stripe.ts << 'TSEOF'
import Stripe from "stripe"

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2024-04-10",
})

export const PLANS = {
  free: {
    name: "Free",
    price: 0,
    features: ["5 projects", "1 team member", "1GB storage"],
    limits: { projects: 5, members: 1, storage: 1 },
  },
  pro: {
    name: "Pro",
    price: 29,
    priceId: "price_pro_monthly",
    features: ["Unlimited projects", "10 team members", "50GB storage", "Priority support"],
    limits: { projects: -1, members: 10, storage: 50 },
  },
  enterprise: {
    name: "Enterprise",
    price: 99,
    priceId: "price_enterprise_monthly",
    features: ["Unlimited everything", "SSO/SAML", "Custom integrations", "SLA 99.9%"],
    limits: { projects: -1, members: -1, storage: -1 },
  },
} as const
TSEOF

# 2. Pricing Page Component
cat > src/components/billing/pricing-cards.tsx << 'TSXEOF'
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Check } from "lucide-react"

const plans = [
  { name: "Free", price: "$0", period: "/month", features: ["5 projects", "1 team member", "1GB storage"], cta: "Get Started", popular: false },
  { name: "Pro", price: "$29", period: "/month", features: ["Unlimited projects", "10 team members", "50GB storage", "Priority support"], cta: "Start Trial", popular: true },
  { name: "Enterprise", price: "$99", period: "/month", features: ["Unlimited everything", "SSO/SAML", "Custom integrations", "SLA 99.9%", "Dedicated support"], cta: "Contact Sales", popular: false },
]

export function PricingCards() {
  return (
    
{plans.map((plan) => ( {plan.popular && Most Popular} {plan.name} {plan.price} {plan.period}
    {plan.features.map((f) => (
  • {f}
  • ))}
))}
) } TSXEOF echo "Billing integration configured"

Performance ????????? Deployment

Optimize ????????? deploy SaaS application

# === Performance & Deployment ===

# 1. Next.js Configuration for SaaS
cat > next.config.ts << 'EOF'
import type { NextConfig } from "next"

const config: NextConfig = {
  // Performance
  reactStrictMode: true,
  poweredByHeader: false,
  
  // Image optimization
  images: {
    formats: ["image/avif", "image/webp"],
    remotePatterns: [
      { protocol: "https", hostname: "*.googleusercontent.com" },
      { protocol: "https", hostname: "avatars.githubusercontent.com" },
    ],
  },
  
  // Headers (Security)
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          { key: "X-Frame-Options", value: "DENY" },
          { key: "X-Content-Type-Options", value: "nosniff" },
          { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
          { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
        ],
      },
    ]
  },
}

export default config
EOF

# 2. Dockerfile
cat > Dockerfile << 'EOF'
FROM node:20-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx prisma generate
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]
EOF

# 3. Vercel Deployment
cat > vercel.json << 'EOF'
{
  "framework": "nextjs",
  "buildCommand": "npx prisma generate && next build",
  "env": {
    "DATABASE_URL": "@database-url",
    "NEXTAUTH_SECRET": "@nextauth-secret",
    "STRIPE_SECRET_KEY": "@stripe-secret-key"
  }
}
EOF

echo "Deployment configured"

FAQ ??????????????????????????????????????????

Q: shadcn/ui ????????? Material UI ???????????? Chakra UI ???????????????????????????????????????????

A: shadcn/ui ?????????????????? npm package ????????? install ????????????????????? copy-paste components ???????????? project ?????????????????? ??????????????? customize ????????? 100% ??????????????? dependency lock-in, bundle size ????????????????????? (???????????????????????? component ??????????????????????????????), update ????????? break ??????????????? code ?????????????????? project Material UI (MUI) ???????????? npm package ????????????????????????????????????????????????????????? ?????? components ?????????????????? ????????????????????? bundle size ???????????? customize ????????? (theming complex) Chakra UI ?????? middle ground ????????????????????? MUI ????????? shadcn/ui ???????????????????????? MUI ?????????????????? lock-in ????????? package ?????????????????? SaaS ??????????????? shadcn/ui ??????????????? customizable ?????????????????? bundle ???????????? ????????? design ????????? modern

Q: Multi-Tenancy ?????? SaaS ????????????????????????????????????????

A: ?????? 3 ?????????????????????????????? Shared Database, Shared Schema ????????? tenant ????????? database ??????????????? ????????????????????? tenantId column ????????????????????? ????????????????????? ???????????????????????????????????? data isolation ????????? query ?????????????????? WHERE tenantId = xxx Shared Database, Separate Schema ????????? schema ????????? tenant ?????? database ??????????????? isolation ?????????????????? migration ??????????????????????????? schema Separate Database 1 database ????????? tenant isolation ???????????????????????? ??????????????????????????? ??????????????? enterprise ?????????????????? SaaS ???????????????????????? ??????????????????????????? (shared database, tenantId column) ???????????????????????????????????? ?????????????????????????????? ????????????????????? enterprise customers ???????????????????????????????????? separate database

Q: SaaS ?????????????????? Stripe ???????????? Paddle ?????????????????? billing?

A: Stripe ???????????? payment processor ?????????????????????????????? tax ????????? (????????????????????? Stripe Tax) ?????? API ????????????????????????????????? flexible ????????? ???????????????????????????????????? 2.9% + 30 cents ????????????????????????????????????????????? control ????????????????????? Paddle ???????????? Merchant of Record ?????????????????? tax, compliance, invoicing ?????????????????????????????? ???????????????????????? Stripe ????????? ???????????????????????????????????? 5% + 50 cents (?????????????????????) ??????????????????????????????????????????????????????????????? tax ???????????????????????? ????????? Stripe ??????????????? ecosystem ???????????????????????? documentation ?????? ????????????????????????????????????????????????????????? ?????????????????? globally ???????????????????????????????????????????????? VAT/GST ????????? Paddle ?????????????????????????????????

Q: SaaS architecture ?????????????????? monolith ???????????? microservices?

A: ?????????????????? startup/early-stage SaaS ????????? Modular Monolith (Next.js full-stack) ?????????????????? ??????????????????????????? develop, deploy ????????????, debug ????????????, ????????? infrastructure ?????????, ????????????????????? (1-5 ??????) ??????????????????????????? ??????????????? scale (50K+ users, 10+ developers) ???????????? extract ???????????? microservices ???????????????????????? ???????????????????????? services ????????????????????? scale ????????? (???????????? billing, notifications, file processing) Next.js + shadcn/ui + Prisma + Stripe ???????????? stack ????????????????????????????????? SaaS monolith ??????????????? MVP ???????????????????????? 2-4 ????????????????????? ???????????????????????? optimize

📖 บทความที่เกี่ยวข้อง

Radix UI Primitives SaaS Architectureอ่านบทความ → Shadcn UI Freelance IT Careerอ่านบทความ → SD-WAN Architecture Message Queue Designอ่านบทความ → Great Expectations SaaS Architectureอ่านบทความ → Prometheus Alertmanager SaaS Architectureอ่านบทความ →

📚 ดูบทความทั้งหมด →

script type="text/javascript"> var _Hasync= _Hasync|| []; _Hasync.push(['Histats.start', '1,4538569,4,0,0,0,00010000']); _Hasync.push(['Histats.fasi', '1']); _Hasync.push(['Histats.track_hits', '']); (function() { var hs = document.createElement('script'); hs.type = 'text/javascript'; hs.async = true; hs.src = ('//s10.histats.com/js15_as.js'); (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(hs); })();