بناء مجموعة بداية 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:
- يُشغل
pnpm create next-appبالأعلام الصحيحة - يُثبت ويُعد Tailwind مع سمة مخصصة
- يُعد Drizzle ORM مع محول PostgreSQL
- يُنشئ هيكل مجموعات المسارات
- يُهيئ shadcn/ui
- يُنشئ .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 لترى أساسك.
النقاط الرئيسية
- AI يُسرع الهيكلة - الإعداد الذي يستغرق ساعات يدوياً يستغرق دقائق مع AI
- الهيكل مهم - مجموعات المسارات والمجلدات المنظمة تجعل سياق AI أكثر فعالية
- أمان الأنواع يُؤتي ثماره - TypeScript والتحقق بـ Zod يلتقطان الأخطاء مبكراً
- أنظمة التصميم تتوسع - الرموز تضمن الاتساق مع نمو تطبيقك
- تصميم قاعدة البيانات أولاً - المخططات المصممة جيداً توفر إعادة الهيكلة لاحقاً
ما التالي
في الدرس التالي، ستُضيف المصادقة وإدارة المستخدمين إلى أساس SaaS الخاص بك باستخدام NextAuth.js مع موفرين متعددين.
:::