أطلق واربح من عملك

استراتيجيات الإطلاق والنمو

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

Building is only half the battle. This lesson covers launching, measuring success, gathering feedback, and iterating based on data.

Analytics Setup

// lib/analytics/posthog.ts
import posthog from 'posthog-js';

export function initAnalytics() {
  if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
    posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
      api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com',
      loaded: (posthog) => {
        if (process.env.NODE_ENV === 'development') {
          posthog.opt_out_capturing();
        }
      },
      capture_pageview: false, // Handle manually for SPA
      autocapture: true,
    });
  }
}

// Track custom events
export function trackEvent(event: string, properties?: Record<string, any>) {
  posthog.capture(event, properties);
}

// Identify users
export function identifyUser(userId: string, traits?: Record<string, any>) {
  posthog.identify(userId, traits);
}

// Track page views
export function trackPageView(url: string) {
  posthog.capture('$pageview', { $current_url: url });
}
// components/analytics/analytics-provider.tsx
'use client';

import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, Suspense } from 'react';
import { initAnalytics, trackPageView, identifyUser } from '@/lib/analytics/posthog';
import { useSession } from 'next-auth/react';

function AnalyticsTracker() {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const { data: session } = useSession();

  useEffect(() => {
    initAnalytics();
  }, []);

  // Track page views
  useEffect(() => {
    const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : '');
    trackPageView(url);
  }, [pathname, searchParams]);

  // Identify user when logged in
  useEffect(() => {
    if (session?.user) {
      identifyUser(session.user.id!, {
        email: session.user.email,
        name: session.user.name,
        plan: (session.user as any).plan,
      });
    }
  }, [session]);

  return null;
}

export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Suspense fallback={null}>
        <AnalyticsTracker />
      </Suspense>
      {children}
    </>
  );
}

Key Metrics to Track

// lib/analytics/events.ts
import { trackEvent } from './posthog';

// Funnel events
export const analytics = {
  // Acquisition
  landingPageView: () => trackEvent('landing_page_view'),
  pricingPageView: () => trackEvent('pricing_page_view'),
  signupStarted: () => trackEvent('signup_started'),
  signupCompleted: (method: string) => trackEvent('signup_completed', { method }),

  // Activation
  onboardingStarted: () => trackEvent('onboarding_started'),
  onboardingStep: (step: number, name: string) => trackEvent('onboarding_step', { step, name }),
  onboardingCompleted: () => trackEvent('onboarding_completed'),
  firstProjectCreated: () => trackEvent('first_project_created'),

  // Engagement
  featureUsed: (feature: string, metadata?: Record<string, any>) =>
    trackEvent('feature_used', { feature, ...metadata }),
  sessionDuration: (seconds: number) => trackEvent('session_duration', { seconds }),

  // Revenue
  checkoutStarted: (plan: string) => trackEvent('checkout_started', { plan }),
  subscriptionCreated: (plan: string, price: number) =>
    trackEvent('subscription_created', { plan, price }),
  subscriptionCanceled: (plan: string, reason?: string) =>
    trackEvent('subscription_canceled', { plan, reason }),

  // Retention
  returnVisit: (daysSinceLastVisit: number) =>
    trackEvent('return_visit', { daysSinceLastVisit }),
};

User Feedback System

// components/feedback/feedback-widget.tsx
'use client';

import { useState } from 'react';
import { analytics } from '@/lib/analytics/events';

type FeedbackType = 'bug' | 'feature' | 'question' | 'praise';

export function FeedbackWidget() {
  const [isOpen, setIsOpen] = useState(false);
  const [type, setType] = useState<FeedbackType>('feature');
  const [message, setMessage] = useState('');
  const [email, setEmail] = useState('');
  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    await fetch('/api/feedback', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ type, message, email }),
    });

    analytics.featureUsed('feedback_submitted', { type });
    setSubmitted(true);

    setTimeout(() => {
      setIsOpen(false);
      setSubmitted(false);
      setMessage('');
    }, 2000);
  };

  return (
    <>
      {/* Trigger button */}
      <button
        onClick={() => setIsOpen(true)}
        className="fixed bottom-4 right-4 bg-blue-500 text-white px-4 py-2 rounded-full shadow-lg hover:bg-blue-600"
      >
        Feedback
      </button>

      {/* Modal */}
      {isOpen && (
        <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
          <div className="bg-white rounded-xl p-6 w-full max-w-md mx-4">
            {submitted ? (
              <div className="text-center py-8">
                <div className="text-4xl mb-4">Thank you!</div>
                <p className="text-gray-600">Your feedback helps us improve.</p>
              </div>
            ) : (
              <form onSubmit={handleSubmit}>
                <div className="flex justify-between items-center mb-4">
                  <h3 className="text-lg font-semibold">Send Feedback</h3>
                  <button type="button" onClick={() => setIsOpen(false)} className="text-gray-400 hover:text-gray-600">
                    x
                  </button>
                </div>

                {/* Type selector */}
                <div className="flex gap-2 mb-4">
                  {(['bug', 'feature', 'question', 'praise'] as const).map((t) => (
                    <button
                      key={t}
                      type="button"
                      onClick={() => setType(t)}
                      className={`px-3 py-1 rounded-full text-sm ${type === t ? 'bg-blue-500 text-white' : 'bg-gray-100'}`}
                    >
                      {t === 'bug' && 'Bug'}
                      {t === 'feature' && 'Feature'}
                      {t === 'question' && 'Question'}
                      {t === 'praise' && 'Praise'}
                    </button>
                  ))}
                </div>

                <textarea
                  value={message}
                  onChange={(e) => setMessage(e.target.value)}
                  placeholder="Tell us what's on your mind..."
                  className="w-full border rounded-lg p-3 h-32 mb-4"
                  required
                />

                <input
                  type="email"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                  placeholder="Email (optional, for follow-up)"
                  className="w-full border rounded-lg p-3 mb-4"
                />

                <button type="submit" className="w-full bg-blue-500 text-white py-3 rounded-lg hover:bg-blue-600">
                  Send Feedback
                </button>
              </form>
            )}
          </div>
        </div>
      )}
    </>
  );
}
// app/api/feedback/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { feedback } from '@/lib/db/schema';

export async function POST(request: NextRequest) {
  const session = await auth();
  const { type, message, email } = await request.json();

  await db.insert(feedback).values({
    id: crypto.randomUUID(),
    userId: session?.user?.id,
    type,
    message,
    email: email || session?.user?.email,
    userAgent: request.headers.get('user-agent'),
    url: request.headers.get('referer'),
    createdAt: new Date(),
  });

  // Optionally send to Slack/Discord
  if (process.env.SLACK_WEBHOOK_URL) {
    await fetch(process.env.SLACK_WEBHOOK_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        text: `New ${type} feedback:\n${message}\n\nFrom: ${email || session?.user?.email || 'Anonymous'}`,
      }),
    });
  }

  return NextResponse.json({ success: true });
}

SEO Optimization

// app/layout.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
  metadataBase: new URL('https://yourapp.com'),
  title: {
    default: 'YourApp - Build faster with AI',
    template: '%s | YourApp',
  },
  description: 'The fastest way to build and ship production apps with AI assistance.',
  keywords: ['ai', 'development', 'productivity', 'saas'],
  authors: [{ name: 'Your Company' }],
  openGraph: {
    type: 'website',
    locale: 'en_US',
    url: 'https://yourapp.com',
    siteName: 'YourApp',
    images: [
      {
        url: '/og-image.png',
        width: 1200,
        height: 630,
        alt: 'YourApp',
      },
    ],
  },
  twitter: {
    card: 'summary_large_image',
    site: '@yourapp',
    creator: '@yourapp',
  },
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1,
    },
  },
};
// app/sitemap.ts
import { MetadataRoute } from 'next';
import { db } from '@/lib/db';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://yourapp.com';

  // Static pages
  const staticPages = [
    { url: baseUrl, lastModified: new Date(), changeFrequency: 'daily' as const, priority: 1 },
    { url: `${baseUrl}/pricing`, lastModified: new Date(), changeFrequency: 'weekly' as const, priority: 0.8 },
    { url: `${baseUrl}/blog`, lastModified: new Date(), changeFrequency: 'daily' as const, priority: 0.7 },
    { url: `${baseUrl}/docs`, lastModified: new Date(), changeFrequency: 'weekly' as const, priority: 0.7 },
  ];

  // Dynamic pages (blog posts, docs, etc.)
  const posts = await db.query.blogPosts.findMany({
    columns: { slug: true, updatedAt: true },
    where: (posts, { eq }) => eq(posts.published, true),
  });

  const blogPages = posts.map((post) => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: post.updatedAt,
    changeFrequency: 'monthly' as const,
    priority: 0.6,
  }));

  return [...staticPages, ...blogPages];
}
// app/robots.ts
import { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/api/', '/dashboard/', '/admin/'],
      },
    ],
    sitemap: 'https://yourapp.com/sitemap.xml',
  };
}

Launch Checklist

// Pre-launch checklist as code comments

/*
## Technical Checklist
- [ ] SSL certificate configured
- [ ] Environment variables set for production
- [ ] Database migrations run
- [ ] Health check endpoint working
- [ ] Error monitoring (Sentry) configured
- [ ] Rate limiting enabled
- [ ] Backup strategy in place
- [ ] CDN configured for static assets

## Security Checklist
- [ ] CORS configured properly
- [ ] CSP headers set
- [ ] No secrets in client-side code
- [ ] Input validation on all endpoints
- [ ] Authentication flows tested
- [ ] Webhook signatures verified

## Analytics & Tracking
- [ ] Google Analytics / PostHog configured
- [ ] Conversion tracking set up
- [ ] Error tracking configured
- [ ] User identification working

## SEO & Marketing
- [ ] Meta tags and OG images
- [ ] Sitemap generated
- [ ] robots.txt configured
- [ ] Landing page optimized
- [ ] Social media profiles created

## Legal & Compliance
- [ ] Privacy policy published
- [ ] Terms of service published
- [ ] Cookie consent (if needed)
- [ ] GDPR compliance (if applicable)

## Payment & Billing
- [ ] Stripe webhooks configured
- [ ] Test payments verified
- [ ] Customer portal working
- [ ] Pricing page accurate
*/

A/B Testing

// lib/experiments/ab-test.ts
import posthog from 'posthog-js';

export function getExperimentVariant(experimentKey: string): string | undefined {
  return posthog.getFeatureFlag(experimentKey) as string | undefined;
}

export function useExperiment(experimentKey: string) {
  const variant = getExperimentVariant(experimentKey);

  return {
    variant,
    isControl: variant === 'control',
    isVariant: (name: string) => variant === name,
  };
}

// Usage in components
export function PricingHero() {
  const { isVariant } = useExperiment('pricing-hero-test');

  if (isVariant('minimal')) {
    return <MinimalPricingHero />;
  }

  if (isVariant('feature-focused')) {
    return <FeatureFocusedHero />;
  }

  return <DefaultPricingHero />;
}

Growth Metrics Dashboard

// app/api/admin/metrics/route.ts
import { NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { users, subscriptions } from '@/lib/db/schema';
import { sql, gte, and, eq } from 'drizzle-orm';

export async function GET() {
  const session = await auth();
  if (session?.user?.role !== 'admin') {
    return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
  }

  const now = new Date();
  const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
  const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);

  const [
    totalUsers,
    newUsersLast30Days,
    newUsersLast7Days,
    activeSubscriptions,
    churnedLast30Days,
  ] = await Promise.all([
    db.select({ count: sql<number>`count(*)` }).from(users),
    db.select({ count: sql<number>`count(*)` }).from(users).where(gte(users.createdAt, thirtyDaysAgo)),
    db.select({ count: sql<number>`count(*)` }).from(users).where(gte(users.createdAt, sevenDaysAgo)),
    db.select({ count: sql<number>`count(*)` }).from(subscriptions).where(eq(subscriptions.status, 'active')),
    db.select({ count: sql<number>`count(*)` }).from(subscriptions).where(
      and(
        eq(subscriptions.status, 'canceled'),
        gte(subscriptions.canceledAt, thirtyDaysAgo)
      )
    ),
  ]);

  // Calculate growth rate
  const growthRate = newUsersLast7Days[0].count / (newUsersLast30Days[0].count / 4) * 100 - 100;

  // Calculate churn rate
  const churnRate = activeSubscriptions[0].count > 0
    ? (churnedLast30Days[0].count / activeSubscriptions[0].count) * 100
    : 0;

  return NextResponse.json({
    totalUsers: totalUsers[0].count,
    newUsersLast30Days: newUsersLast30Days[0].count,
    newUsersLast7Days: newUsersLast7Days[0].count,
    activeSubscriptions: activeSubscriptions[0].count,
    growthRate: growthRate.toFixed(1),
    churnRate: churnRate.toFixed(1),
  });
}

Email Campaigns

// lib/email/campaigns.ts
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendWelcomeEmail(email: string, name: string) {
  await resend.emails.send({
    from: 'hello@yourapp.com',
    to: email,
    subject: 'Welcome to YourApp!',
    react: WelcomeEmailTemplate({ name }),
  });
}

export async function sendTrialEndingEmail(email: string, name: string, daysLeft: number) {
  await resend.emails.send({
    from: 'hello@yourapp.com',
    to: email,
    subject: `Your trial ends in ${daysLeft} days`,
    react: TrialEndingTemplate({ name, daysLeft }),
  });
}

export async function sendWeeklyDigest(email: string, stats: UserStats) {
  await resend.emails.send({
    from: 'hello@yourapp.com',
    to: email,
    subject: 'Your weekly digest',
    react: WeeklyDigestTemplate(stats),
  });
}

What You've Learned

In this lesson, you've implemented:

  • Analytics setup - PostHog for user behavior tracking
  • Key metrics - Funnel events and conversion tracking
  • Feedback system - In-app feedback collection
  • SEO optimization - Meta tags, sitemap, and robots.txt
  • Growth tracking - Dashboard for business metrics

Launching is just the beginning. Use data to understand users, iterate on feedback, and grow sustainably.

Course Conclusion

Congratulations on completing Project-Based Vibe Coding! You've built:

  1. SaaS Starter Kit - Auth, billing, multi-tenancy
  2. Mobile Apps - Expo, native features, app store deployment
  3. Developer Tools - CLI, VS Code extensions, API clients
  4. AI Features - Chat, semantic search, content generation
  5. Real-Time Apps - WebSockets, collaboration, live dashboards
  6. Ship & Monetize - Deployment, payments, growth

You now have the skills to build and ship production applications. Keep building, keep learning, and most importantly, keep shipping!

Next Course: Full Stack AI Applications - Take your AI integration skills to the next level with agents, RAG, and advanced patterns.


استراتيجيات الإطلاق والنمو

البناء هو نصف المعركة فقط. هذا الدرس يغطي الإطلاق وقياس النجاح وجمع الملاحظات والتكرار بناءً على البيانات.

إعداد التحليلات

// lib/analytics/posthog.ts
import posthog from 'posthog-js';

export function initAnalytics() {
  if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
    posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
      api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com',
      loaded: (posthog) => {
        if (process.env.NODE_ENV === 'development') {
          posthog.opt_out_capturing();
        }
      },
      capture_pageview: false, // المعالجة يدوياً لـ SPA
      autocapture: true,
    });
  }
}

// تتبع الأحداث المخصصة
export function trackEvent(event: string, properties?: Record<string, any>) {
  posthog.capture(event, properties);
}

// تحديد المستخدمين
export function identifyUser(userId: string, traits?: Record<string, any>) {
  posthog.identify(userId, traits);
}

// تتبع مشاهدات الصفحات
export function trackPageView(url: string) {
  posthog.capture('$pageview', { $current_url: url });
}
// components/analytics/analytics-provider.tsx
'use client';

import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, Suspense } from 'react';
import { initAnalytics, trackPageView, identifyUser } from '@/lib/analytics/posthog';
import { useSession } from 'next-auth/react';

function AnalyticsTracker() {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const { data: session } = useSession();

  useEffect(() => {
    initAnalytics();
  }, []);

  // تتبع مشاهدات الصفحات
  useEffect(() => {
    const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : '');
    trackPageView(url);
  }, [pathname, searchParams]);

  // تحديد المستخدم عند تسجيل الدخول
  useEffect(() => {
    if (session?.user) {
      identifyUser(session.user.id!, {
        email: session.user.email,
        name: session.user.name,
        plan: (session.user as any).plan,
      });
    }
  }, [session]);

  return null;
}

export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Suspense fallback={null}>
        <AnalyticsTracker />
      </Suspense>
      {children}
    </>
  );
}

المقاييس الرئيسية للتتبع

// lib/analytics/events.ts
import { trackEvent } from './posthog';

// أحداث القمع
export const analytics = {
  // الاستحواذ
  landingPageView: () => trackEvent('landing_page_view'),
  pricingPageView: () => trackEvent('pricing_page_view'),
  signupStarted: () => trackEvent('signup_started'),
  signupCompleted: (method: string) => trackEvent('signup_completed', { method }),

  // التفعيل
  onboardingStarted: () => trackEvent('onboarding_started'),
  onboardingStep: (step: number, name: string) => trackEvent('onboarding_step', { step, name }),
  onboardingCompleted: () => trackEvent('onboarding_completed'),
  firstProjectCreated: () => trackEvent('first_project_created'),

  // التفاعل
  featureUsed: (feature: string, metadata?: Record<string, any>) =>
    trackEvent('feature_used', { feature, ...metadata }),
  sessionDuration: (seconds: number) => trackEvent('session_duration', { seconds }),

  // الإيرادات
  checkoutStarted: (plan: string) => trackEvent('checkout_started', { plan }),
  subscriptionCreated: (plan: string, price: number) =>
    trackEvent('subscription_created', { plan, price }),
  subscriptionCanceled: (plan: string, reason?: string) =>
    trackEvent('subscription_canceled', { plan, reason }),

  // الاحتفاظ
  returnVisit: (daysSinceLastVisit: number) =>
    trackEvent('return_visit', { daysSinceLastVisit }),
};

نظام ملاحظات المستخدم

// components/feedback/feedback-widget.tsx
'use client';

import { useState } from 'react';
import { analytics } from '@/lib/analytics/events';

type FeedbackType = 'bug' | 'feature' | 'question' | 'praise';

export function FeedbackWidget() {
  const [isOpen, setIsOpen] = useState(false);
  const [type, setType] = useState<FeedbackType>('feature');
  const [message, setMessage] = useState('');
  const [email, setEmail] = useState('');
  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    await fetch('/api/feedback', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ type, message, email }),
    });

    analytics.featureUsed('feedback_submitted', { type });
    setSubmitted(true);

    setTimeout(() => {
      setIsOpen(false);
      setSubmitted(false);
      setMessage('');
    }, 2000);
  };

  return (
    <>
      {/* زر التشغيل */}
      <button
        onClick={() => setIsOpen(true)}
        className="fixed bottom-4 right-4 bg-blue-500 text-white px-4 py-2 rounded-full shadow-lg hover:bg-blue-600"
      >
        ملاحظات
      </button>

      {/* النافذة المنبثقة */}
      {isOpen && (
        <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
          <div className="bg-white rounded-xl p-6 w-full max-w-md mx-4">
            {submitted ? (
              <div className="text-center py-8">
                <div className="text-4xl mb-4">شكراً لك!</div>
                <p className="text-gray-600">ملاحظاتك تساعدنا على التحسين.</p>
              </div>
            ) : (
              <form onSubmit={handleSubmit}>
                <div className="flex justify-between items-center mb-4">
                  <h3 className="text-lg font-semibold">إرسال ملاحظات</h3>
                  <button type="button" onClick={() => setIsOpen(false)} className="text-gray-400 hover:text-gray-600">
                    x
                  </button>
                </div>

                {/* محدد النوع */}
                <div className="flex gap-2 mb-4">
                  {(['bug', 'feature', 'question', 'praise'] as const).map((t) => (
                    <button
                      key={t}
                      type="button"
                      onClick={() => setType(t)}
                      className={`px-3 py-1 rounded-full text-sm ${type === t ? 'bg-blue-500 text-white' : 'bg-gray-100'}`}
                    >
                      {t === 'bug' && 'خطأ'}
                      {t === 'feature' && 'ميزة'}
                      {t === 'question' && 'سؤال'}
                      {t === 'praise' && 'إشادة'}
                    </button>
                  ))}
                </div>

                <textarea
                  value={message}
                  onChange={(e) => setMessage(e.target.value)}
                  placeholder="أخبرنا ما في بالك..."
                  className="w-full border rounded-lg p-3 h-32 mb-4"
                  required
                />

                <input
                  type="email"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                  placeholder="البريد الإلكتروني (اختياري، للمتابعة)"
                  className="w-full border rounded-lg p-3 mb-4"
                />

                <button type="submit" className="w-full bg-blue-500 text-white py-3 rounded-lg hover:bg-blue-600">
                  إرسال الملاحظات
                </button>
              </form>
            )}
          </div>
        </div>
      )}
    </>
  );
}
// app/api/feedback/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { feedback } from '@/lib/db/schema';

export async function POST(request: NextRequest) {
  const session = await auth();
  const { type, message, email } = await request.json();

  await db.insert(feedback).values({
    id: crypto.randomUUID(),
    userId: session?.user?.id,
    type,
    message,
    email: email || session?.user?.email,
    userAgent: request.headers.get('user-agent'),
    url: request.headers.get('referer'),
    createdAt: new Date(),
  });

  // اختيارياً الإرسال إلى Slack/Discord
  if (process.env.SLACK_WEBHOOK_URL) {
    await fetch(process.env.SLACK_WEBHOOK_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        text: `ملاحظات ${type} جديدة:\n${message}\n\nمن: ${email || session?.user?.email || 'مجهول'}`,
      }),
    });
  }

  return NextResponse.json({ success: true });
}

تحسين SEO

// app/layout.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
  metadataBase: new URL('https://yourapp.com'),
  title: {
    default: 'YourApp - ابنِ أسرع مع AI',
    template: '%s | YourApp',
  },
  description: 'أسرع طريقة لبناء وإطلاق تطبيقات الإنتاج بمساعدة AI.',
  keywords: ['ai', 'تطوير', 'إنتاجية', 'saas'],
  authors: [{ name: 'شركتك' }],
  openGraph: {
    type: 'website',
    locale: 'ar_SA',
    url: 'https://yourapp.com',
    siteName: 'YourApp',
    images: [
      {
        url: '/og-image.png',
        width: 1200,
        height: 630,
        alt: 'YourApp',
      },
    ],
  },
  twitter: {
    card: 'summary_large_image',
    site: '@yourapp',
    creator: '@yourapp',
  },
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1,
    },
  },
};
// app/sitemap.ts
import { MetadataRoute } from 'next';
import { db } from '@/lib/db';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://yourapp.com';

  // الصفحات الثابتة
  const staticPages = [
    { url: baseUrl, lastModified: new Date(), changeFrequency: 'daily' as const, priority: 1 },
    { url: `${baseUrl}/pricing`, lastModified: new Date(), changeFrequency: 'weekly' as const, priority: 0.8 },
    { url: `${baseUrl}/blog`, lastModified: new Date(), changeFrequency: 'daily' as const, priority: 0.7 },
    { url: `${baseUrl}/docs`, lastModified: new Date(), changeFrequency: 'weekly' as const, priority: 0.7 },
  ];

  // الصفحات الديناميكية (مقالات المدونة، التوثيق، إلخ)
  const posts = await db.query.blogPosts.findMany({
    columns: { slug: true, updatedAt: true },
    where: (posts, { eq }) => eq(posts.published, true),
  });

  const blogPages = posts.map((post) => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: post.updatedAt,
    changeFrequency: 'monthly' as const,
    priority: 0.6,
  }));

  return [...staticPages, ...blogPages];
}
// app/robots.ts
import { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/api/', '/dashboard/', '/admin/'],
      },
    ],
    sitemap: 'https://yourapp.com/sitemap.xml',
  };
}

قائمة مراجعة الإطلاق

// قائمة مراجعة ما قبل الإطلاق كتعليقات كود

/*
## القائمة التقنية
- [ ] شهادة SSL مكوّنة
- [ ] متغيرات البيئة معيّنة للإنتاج
- [ ] ترحيلات قاعدة البيانات مُشغّلة
- [ ] نقطة نهاية فحص الصحة تعمل
- [ ] مراقبة الأخطاء (Sentry) مكوّنة
- [ ] تحديد المعدل مُفعّل
- [ ] استراتيجية النسخ الاحتياطي موجودة
- [ ] CDN مكوّن للأصول الثابتة

## قائمة الأمان
- [ ] CORS مكوّن بشكل صحيح
- [ ] ترويسات CSP معيّنة
- [ ] لا أسرار في كود جانب العميل
- [ ] التحقق من المدخلات على جميع نقاط النهاية
- [ ] تدفقات المصادقة مُختبرة
- [ ] توقيعات Webhook مُتحققة

## التحليلات والتتبع
- [ ] Google Analytics / PostHog مكوّن
- [ ] تتبع التحويلات معدّ
- [ ] تتبع الأخطاء مكوّن
- [ ] تحديد المستخدم يعمل

## SEO والتسويق
- [ ] وسوم Meta وصور OG
- [ ] خريطة الموقع مولّدة
- [ ] robots.txt مكوّن
- [ ] صفحة الهبوط محسّنة
- [ ] ملفات وسائل التواصل الاجتماعي مُنشأة

## القانوني والامتثال
- [ ] سياسة الخصوصية منشورة
- [ ] شروط الخدمة منشورة
- [ ] موافقة ملفات تعريف الارتباط (إذا لزم)
- [ ] امتثال GDPR (إذا كان قابلاً للتطبيق)

## الدفع والفوترة
- [ ] Stripe webhooks مكوّنة
- [ ] المدفوعات التجريبية مُتحققة
- [ ] بوابة العملاء تعمل
- [ ] صفحة التسعير دقيقة
*/

اختبار A/B

// lib/experiments/ab-test.ts
import posthog from 'posthog-js';

export function getExperimentVariant(experimentKey: string): string | undefined {
  return posthog.getFeatureFlag(experimentKey) as string | undefined;
}

export function useExperiment(experimentKey: string) {
  const variant = getExperimentVariant(experimentKey);

  return {
    variant,
    isControl: variant === 'control',
    isVariant: (name: string) => variant === name,
  };
}

// الاستخدام في المكونات
export function PricingHero() {
  const { isVariant } = useExperiment('pricing-hero-test');

  if (isVariant('minimal')) {
    return <MinimalPricingHero />;
  }

  if (isVariant('feature-focused')) {
    return <FeatureFocusedHero />;
  }

  return <DefaultPricingHero />;
}

لوحة معلومات مقاييس النمو

// app/api/admin/metrics/route.ts
import { NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { users, subscriptions } from '@/lib/db/schema';
import { sql, gte, and, eq } from 'drizzle-orm';

export async function GET() {
  const session = await auth();
  if (session?.user?.role !== 'admin') {
    return NextResponse.json({ error: 'ممنوع' }, { status: 403 });
  }

  const now = new Date();
  const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
  const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);

  const [
    totalUsers,
    newUsersLast30Days,
    newUsersLast7Days,
    activeSubscriptions,
    churnedLast30Days,
  ] = await Promise.all([
    db.select({ count: sql<number>`count(*)` }).from(users),
    db.select({ count: sql<number>`count(*)` }).from(users).where(gte(users.createdAt, thirtyDaysAgo)),
    db.select({ count: sql<number>`count(*)` }).from(users).where(gte(users.createdAt, sevenDaysAgo)),
    db.select({ count: sql<number>`count(*)` }).from(subscriptions).where(eq(subscriptions.status, 'active')),
    db.select({ count: sql<number>`count(*)` }).from(subscriptions).where(
      and(
        eq(subscriptions.status, 'canceled'),
        gte(subscriptions.canceledAt, thirtyDaysAgo)
      )
    ),
  ]);

  // حساب معدل النمو
  const growthRate = newUsersLast7Days[0].count / (newUsersLast30Days[0].count / 4) * 100 - 100;

  // حساب معدل الانسحاب
  const churnRate = activeSubscriptions[0].count > 0
    ? (churnedLast30Days[0].count / activeSubscriptions[0].count) * 100
    : 0;

  return NextResponse.json({
    totalUsers: totalUsers[0].count,
    newUsersLast30Days: newUsersLast30Days[0].count,
    newUsersLast7Days: newUsersLast7Days[0].count,
    activeSubscriptions: activeSubscriptions[0].count,
    growthRate: growthRate.toFixed(1),
    churnRate: churnRate.toFixed(1),
  });
}

حملات البريد الإلكتروني

// lib/email/campaigns.ts
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendWelcomeEmail(email: string, name: string) {
  await resend.emails.send({
    from: 'hello@yourapp.com',
    to: email,
    subject: 'مرحباً بك في YourApp!',
    react: WelcomeEmailTemplate({ name }),
  });
}

export async function sendTrialEndingEmail(email: string, name: string, daysLeft: number) {
  await resend.emails.send({
    from: 'hello@yourapp.com',
    to: email,
    subject: `تجربتك تنتهي خلال ${daysLeft} أيام`,
    react: TrialEndingTemplate({ name, daysLeft }),
  });
}

export async function sendWeeklyDigest(email: string, stats: UserStats) {
  await resend.emails.send({
    from: 'hello@yourapp.com',
    to: email,
    subject: 'ملخصك الأسبوعي',
    react: WeeklyDigestTemplate(stats),
  });
}

ما تعلمته

في هذا الدرس، نفذت:

  • إعداد التحليلات - PostHog لتتبع سلوك المستخدم
  • المقاييس الرئيسية - أحداث القمع وتتبع التحويلات
  • نظام الملاحظات - جمع الملاحظات داخل التطبيق
  • تحسين SEO - وسوم Meta وخريطة الموقع وrobots.txt
  • تتبع النمو - لوحة معلومات لمقاييس الأعمال

الإطلاق هو البداية فقط. استخدم البيانات لفهم المستخدمين، والتكرار على الملاحظات، والنمو بشكل مستدام.

خاتمة الدورة

تهانينا على إكمال Project-Based Vibe Coding! لقد بنيت:

  1. مجموعة بداية SaaS - المصادقة، الفوترة، تعدد المستأجرين
  2. تطبيقات الموبايل - Expo، الميزات الأصلية، نشر متجر التطبيقات
  3. أدوات المطورين - CLI، إضافات VS Code، عملاء API
  4. ميزات AI - الدردشة، البحث الدلالي، توليد المحتوى
  5. تطبيقات الوقت الحقيقي - WebSockets، التعاون، لوحات المعلومات الحية
  6. الإطلاق وتحقيق الدخل - النشر، المدفوعات، النمو

لديك الآن المهارات لبناء وإطلاق تطبيقات الإنتاج. استمر في البناء، استمر في التعلم، والأهم، استمر في الإطلاق!

الدورة التالية: تطبيقات AI Full Stack - ارتقِ بمهارات تكامل AI إلى المستوى التالي مع الوكلاء وRAG والأنماط المتقدمة.

اختبار

الوحدة 6: أطلق واربح من عملك

خذ الاختبار