بناء مجموعة بداية SaaS

إعداد وهندسة مشروع SaaS

5 دقيقة للقراءة

ما ستبنيه

بنهاية هذا الدرس، سيكون لديك أساس SaaS كامل:

  • Next.js 15 مع App Router وTypeScript
  • Tailwind CSS مع نظام تصميم احترافي
  • قاعدة بيانات PostgreSQL مع Drizzle ORM
  • هيكل مشروع محسّن للتطوير بمساعدة AI
  • إعداد البيئة للتطوير والإنتاج

الوقت للإكمال: ~30 دقيقة بمساعدة AI (مقابل 2-3 ساعات يدوياً)


الخطوة 1: التهيئة مع AI

افتح الطرفية وابدأ Claude Code (أو Cursor مع وضع الوكيل). استخدم هذا الأمر:

Create a new Next.js 15 SaaS starter with:
- TypeScript in strict mode
- Tailwind CSS with a custom color palette (primary: indigo, secondary: slate)
- App Router with (marketing), (dashboard), and (auth) route groups
- PostgreSQL with Drizzle ORM
- shadcn/ui components (button, card, input, dialog)
- Environment variables setup (.env.example)

Project name: my-saas-starter
Use pnpm as package manager

ما يفعله AI:

  1. يُشغل pnpm create next-app بالأعلام الصحيحة
  2. يُثبت ويُعد Tailwind مع سمة مخصصة
  3. يُعد Drizzle ORM مع محول PostgreSQL
  4. يُنشئ هيكل مجموعات المسارات
  5. يُهيئ shadcn/ui
  6. يُنشئ .env.example بالمتغيرات المطلوبة

الخطوة 2: هيكل المشروع

بعد أن يُكمل AI الإعداد، يجب أن يكون لديك:

my-saas-starter/
├── src/
│   ├── app/
│   │   ├── (marketing)/          # الهبوط، التسعير، حول
│   │   │   ├── page.tsx          # الصفحة الرئيسية
│   │   │   ├── pricing/
│   │   │   └── layout.tsx
│   │   ├── (dashboard)/          # صفحات التطبيق المحمية
│   │   │   ├── dashboard/
│   │   │   ├── settings/
│   │   │   └── layout.tsx
│   │   ├── (auth)/               # تسجيل الدخول، التسجيل، استعادة كلمة المرور
│   │   │   ├── login/
│   │   │   ├── signup/
│   │   │   └── layout.tsx
│   │   ├── api/                  # مسارات API
│   │   ├── layout.tsx            # التخطيط الجذري
│   │   └── globals.css
│   ├── components/
│   │   ├── ui/                   # مكونات shadcn
│   │   ├── marketing/            # مكونات صفحة الهبوط
│   │   └── dashboard/            # مكونات لوحة التحكم
│   ├── lib/
│   │   ├── db/                   # عميل قاعدة البيانات والمخطط
│   │   ├── auth/                 # أدوات المصادقة
│   │   └── utils.ts
│   └── types/
├── drizzle/                      # الترحيلات
├── public/
├── .env.example
├── drizzle.config.ts
├── tailwind.config.ts
└── package.json

الخطوة 3: تصميم مخطط قاعدة البيانات

اطلب من AI إنشاء مخطط قاعدة البيانات الأولي:

Create a Drizzle schema for a multi-tenant SaaS with:
- users table (id, email, name, avatar, created_at, updated_at)
- organizations table (id, name, slug, owner_id, plan, created_at)
- organization_members table (user_id, org_id, role: owner/admin/member)
- subscriptions table (id, org_id, stripe_customer_id, stripe_subscription_id, status, plan, current_period_end)

Use UUID for IDs, proper indexes, and foreign key relationships.
File: src/lib/db/schema.ts

AI يُنشئ:

// src/lib/db/schema.ts
import { pgTable, uuid, text, timestamp, pgEnum, index } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';

// التعدادات
export const memberRoleEnum = pgEnum('member_role', ['owner', 'admin', 'member']);
export const subscriptionStatusEnum = pgEnum('subscription_status', [
  'active', 'canceled', 'past_due', 'trialing', 'incomplete'
]);
export const planEnum = pgEnum('plan', ['free', 'starter', 'pro', 'enterprise']);

// المستخدمون
export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: text('email').notNull().unique(),
  name: text('name'),
  avatarUrl: text('avatar_url'),
  createdAt: timestamp('created_at').defaultNow().notNull(),
  updatedAt: timestamp('updated_at').defaultNow().notNull(),
}, (table) => ({
  emailIdx: index('users_email_idx').on(table.email),
}));

// المنظمات
export const organizations = pgTable('organizations', {
  id: uuid('id').primaryKey().defaultRandom(),
  name: text('name').notNull(),
  slug: text('slug').notNull().unique(),
  ownerId: uuid('owner_id').references(() => users.id).notNull(),
  plan: planEnum('plan').default('free').notNull(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
}, (table) => ({
  slugIdx: index('orgs_slug_idx').on(table.slug),
  ownerIdx: index('orgs_owner_idx').on(table.ownerId),
}));

// أعضاء المنظمة
export const organizationMembers = pgTable('organization_members', {
  userId: uuid('user_id').references(() => users.id).notNull(),
  organizationId: uuid('organization_id').references(() => organizations.id).notNull(),
  role: memberRoleEnum('role').default('member').notNull(),
  joinedAt: timestamp('joined_at').defaultNow().notNull(),
}, (table) => ({
  pk: index('org_members_pk').on(table.userId, table.organizationId),
}));

// الاشتراكات
export const subscriptions = pgTable('subscriptions', {
  id: uuid('id').primaryKey().defaultRandom(),
  organizationId: uuid('organization_id').references(() => organizations.id).notNull(),
  stripeCustomerId: text('stripe_customer_id'),
  stripeSubscriptionId: text('stripe_subscription_id'),
  status: subscriptionStatusEnum('status').default('trialing').notNull(),
  plan: planEnum('plan').default('free').notNull(),
  currentPeriodEnd: timestamp('current_period_end'),
  createdAt: timestamp('created_at').defaultNow().notNull(),
}, (table) => ({
  orgIdx: index('subscriptions_org_idx').on(table.organizationId),
  stripeSubIdx: index('subscriptions_stripe_idx').on(table.stripeSubscriptionId),
}));

// العلاقات
export const usersRelations = relations(users, ({ many }) => ({
  ownedOrganizations: many(organizations),
  memberships: many(organizationMembers),
}));

export const organizationsRelations = relations(organizations, ({ one, many }) => ({
  owner: one(users, {
    fields: [organizations.ownerId],
    references: [users.id],
  }),
  members: many(organizationMembers),
  subscription: one(subscriptions),
}));

الخطوة 4: إعداد البيئة

أنشئ معالجة بيئة مناسبة:

Create environment configuration with:
1. .env.example with all required variables (documented)
2. src/lib/env.ts with Zod validation for type-safe env access
3. Include: DATABASE_URL, NEXTAUTH_*, STRIPE_*, APP_URL

AI يُنشئ:

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

const envSchema = z.object({
  // قاعدة البيانات
  DATABASE_URL: z.string().url(),

  // المصادقة
  NEXTAUTH_SECRET: z.string().min(32),
  NEXTAUTH_URL: z.string().url(),

  // موفرو OAuth
  GOOGLE_CLIENT_ID: z.string().optional(),
  GOOGLE_CLIENT_SECRET: z.string().optional(),
  GITHUB_CLIENT_ID: z.string().optional(),
  GITHUB_CLIENT_SECRET: z.string().optional(),

  // Stripe
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
  STRIPE_PUBLISHABLE_KEY: z.string().startsWith('pk_'),
  STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_'),

  // التطبيق
  NEXT_PUBLIC_APP_URL: z.string().url(),

  // اختياري
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
});

// التحليل والتحقق
const parsed = envSchema.safeParse(process.env);

if (!parsed.success) {
  console.error('❌ متغيرات بيئة غير صالحة:');
  console.error(parsed.error.flatten().fieldErrors);
  throw new Error('متغيرات بيئة غير صالحة');
}

export const env = parsed.data;

الخطوة 5: إعداد نظام التصميم

اطلب من AI إنشاء نظام تصميم متسق:

Create a Tailwind design system config with:
- Primary color: Indigo (for CTAs, links)
- Secondary color: Slate (for text, backgrounds)
- Success/Warning/Error colors
- Typography scale (display, heading, body, small)
- Spacing consistent with 4px grid
- Border radius tokens (sm, md, lg, xl, full)
- Shadow tokens (sm, md, lg, glow)
- Animation durations

Update tailwind.config.ts with these tokens.

الخطوة 6: إنشاء المكونات الأساسية

استخدم AI لتوليد المكونات التأسيسية:

Create these base components in src/components/:

1. ui/container.tsx - Max-width centered container with responsive padding
2. ui/section.tsx - Page section with vertical spacing
3. ui/heading.tsx - Typography component with size variants
4. marketing/navbar.tsx - Marketing site header with mobile menu
5. marketing/footer.tsx - Site footer with links
6. dashboard/sidebar.tsx - Dashboard navigation sidebar
7. dashboard/header.tsx - Dashboard top bar with user menu

Use shadcn/ui components where appropriate.
Follow the design system tokens.
Include TypeScript types for all props.

الخطوة 7: إعداد قاعدة البيانات

شغّل الترحيل الأولي:

# إنشاء قاعدة البيانات (باستخدام Docker للتطوير المحلي)
docker run --name saas-postgres -e POSTGRES_PASSWORD=password -e POSTGRES_DB=saas -p 5432:5432 -d postgres:16

# توليد الترحيل
pnpm drizzle-kit generate

# الدفع لقاعدة البيانات
pnpm drizzle-kit push

اطلب من AI إنشاء أداة قاعدة البيانات:

Create src/lib/db/index.ts with:
- Database client singleton
- Helper function for transactions
- Connection pooling configuration for serverless (Neon/Vercel Postgres)

الخطوة 8: التحقق من الإعداد

اطلب من AI إنشاء صفحة اختبار بسيطة:

Create a test page at src/app/(marketing)/page.tsx that:
- Uses the Container and Section components
- Shows the Navbar and Footer
- Displays a hero section with headline, subheadline, and CTA button
- Shows 3 feature cards using shadcn Card component
- Is fully responsive

Hero text:
- Headline: "Build faster with AI-powered development"
- Subheadline: "The modern SaaS starter kit for vibe coders"
- CTA: "Get Started Free"

شغّل خادم التطوير:

pnpm dev

زُر http://localhost:3000 لترى أساسك.


النقاط الرئيسية

  1. AI يُسرع الهيكلة - الإعداد الذي يستغرق ساعات يدوياً يستغرق دقائق مع AI
  2. الهيكل مهم - مجموعات المسارات والمجلدات المنظمة تجعل سياق AI أكثر فعالية
  3. أمان الأنواع يُؤتي ثماره - TypeScript والتحقق بـ Zod يلتقطان الأخطاء مبكراً
  4. أنظمة التصميم تتوسع - الرموز تضمن الاتساق مع نمو تطبيقك
  5. تصميم قاعدة البيانات أولاً - المخططات المصممة جيداً توفر إعادة الهيكلة لاحقاً

ما التالي

في الدرس التالي، ستُضيف المصادقة وإدارة المستخدمين إلى أساس SaaS الخاص بك باستخدام NextAuth.js مع موفرين متعددين.

:::

اختبار

الوحدة 1: بناء مجموعة بداية SaaS

خذ الاختبار