إطار Next.js

التحليلات والمراقبة في Next.js

40 دقيقة الدرس 36 من 40

التحليلات والمراقبة في Next.js

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

لماذا تهم التحليلات والمراقبة

تساعدك مراقبة الإنتاج على:

  • تتبع سلوك المستخدم: فهم كيفية تفاعل المستخدمين مع تطبيقك
  • تحديد اختناقات الأداء: العثور على الصفحات البطيئة ومسارات API
  • اكتشاف الأخطاء مبكراً: اكتشاف وإصلاح المشكلات قبل أن تؤثر على المستخدمين
  • اتخاذ قرارات مبنية على البيانات: استخدام المقاييس الحقيقية لتوجيه أولويات التطوير
  • مراقبة المقاييس الحيوية الأساسية: تتبع المقاييس التي تؤثر على SEO وتجربة المستخدم

تكامل Vercel Analytics

يوفر Vercel Analytics مراقبة أداء مدمجة لتطبيقات Next.js المنشورة على Vercel:

تثبيت Vercel Analytics:
npm install @vercel/analytics
app/layout.tsx:
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        {children}
        <Analytics />
        <SpeedInsights />
      </body>
    </html>
  );
}
نصيحة: يتتبع مكون Analytics تلقائياً مشاهدات الصفحة، والمقاييس الحيوية للويب (LCP، FID، CLS)، ومقاييس الأداء الأخرى دون الحاجة إلى تكوين إضافي.

تتبع الأحداث المخصصة

تتبع الأحداث المخصصة لفهم تفاعلات المستخدم المحددة:

components/ProductCard.tsx:
'use client';

import { track } from '@vercel/analytics';

export default function ProductCard({ product }: { product: Product }) {
  const handleAddToCart = () => {
    // تتبع حدث مخصص
    track('add_to_cart', {
      product_id: product.id,
      product_name: product.name,
      price: product.price,
      category: product.category,
    });

    // منطق إضافة إلى السلة
    addToCart(product);
  };

  return (
    <div className="product-card">
      <h3>{product.name}</h3>
      <p>{product.price} ريال</p>
      <button onClick={handleAddToCart}>
        أضف إلى السلة
      </button>
    </div>
  );
}
أحداث مخصصة شائعة:
// تتبع البحث
track('search', {
  query: searchTerm,
  results_count: results.length,
});

// استخدام الميزة
track('feature_used', {
  feature_name: 'dark_mode',
  enabled: isDarkMode,
});

// تتبع التحويل
track('purchase_completed', {
  order_id: order.id,
  total: order.total,
  items_count: order.items.length,
});

// تتبع التفاعل
track('video_played', {
  video_id: video.id,
  duration: video.duration,
  timestamp: currentTime,
});

تكامل Google Analytics 4

دمج GA4 للتحليلات الشاملة:

تثبيت مكتبة GA:
npm install @next/third-parties
app/layout.tsx:
import { GoogleAnalytics } from '@next/third-parties/google';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        {children}
        <GoogleAnalytics gaId="G-XXXXXXXXXX" />
      </body>
    </html>
  );
}
lib/analytics.ts (أحداث مخصصة):
export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID;

// تتبع مشاهدات الصفحة
export const pageview = (url: string) => {
  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('config', GA_TRACKING_ID, {
      page_path: url,
    });
  }
};

// تتبع الأحداث
export const event = ({
  action,
  category,
  label,
  value,
}: {
  action: string;
  category: string;
  label: string;
  value?: number;
}) => {
  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('event', action, {
      event_category: category,
      event_label: label,
      value: value,
    });
  }
};
الاستخدام في المكونات:
'use client';

import { event } from '@/lib/analytics';

export default function NewsletterForm() {
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    // تتبع الحدث
    event({
      action: 'submit',
      category: 'Newsletter',
      label: 'Subscription Form',
    });

    // منطق الإرسال
    await subscribeToNewsletter(email);
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* حقول النموذج */}
    </form>
  );
}

تتبع الأخطاء مع Sentry

يوفر Sentry تتبعاً شاملاً للأخطاء ومراقبة الأداء:

تثبيت Sentry:
npx @sentry/wizard@latest -i nextjs
sentry.client.config.ts:
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,

  // مراقبة الأداء
  tracesSampleRate: 1.0,

  // إعادة تشغيل الجلسة
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,

  // البيئة
  environment: process.env.NODE_ENV,

  // تتبع الإصدار
  release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
});
app/error.tsx (حد الخطأ):
'use client';

import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // تسجيل الخطأ في Sentry
    Sentry.captureException(error);
  }, [error]);

  return (
    <div className="error-container">
      <h2>حدث خطأ ما!</h2>
      <button onClick={reset}>حاول مرة أخرى</button>
    </div>
  );
}
تتبع الأخطاء اليدوي:
import * as Sentry from '@sentry/nextjs';

async function fetchUserData(userId: string) {
  try {
    const response = await fetch(\`/api/users/${userId}\`);

    if (!response.ok) {
      throw new Error(\`فشل جلب المستخدم: ${response.status}\`);
    }

    return await response.json();
  } catch (error) {
    // التقاط الخطأ مع السياق
    Sentry.captureException(error, {
      tags: {
        section: 'user_data',
      },
      extra: {
        userId,
        url: \`/api/users/${userId}\`,
      },
    });

    throw error;
  }
}

مراقبة الأداء

مراقبة المقاييس الحيوية الأساسية ومقاييس الأداء المخصصة:

app/layout.tsx:
import { WebVitals } from './web-vitals';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        {children}
        <WebVitals />
      </body>
    </html>
  );
}
app/web-vitals.tsx:
'use client';

import { useReportWebVitals } from 'next/web-vitals';

export function WebVitals() {
  useReportWebVitals((metric) => {
    // السجل إلى وحدة التحكم في التطوير
    if (process.env.NODE_ENV === 'development') {
      console.log(metric);
    }

    // إرسال إلى خدمة التحليلات
    const body = JSON.stringify({
      name: metric.name,
      value: metric.value,
      rating: metric.rating,
      delta: metric.delta,
      id: metric.id,
    });

    // إرسال إلى نقطة نهاية التحليلات الخاصة بك
    if (navigator.sendBeacon) {
      navigator.sendBeacon('/api/analytics/web-vitals', body);
    } else {
      fetch('/api/analytics/web-vitals', {
        method: 'POST',
        body,
        keepalive: true,
      });
    }
  });

  return null;
}
app/api/analytics/web-vitals/route.ts:
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const metric = await request.json();

  // التخزين في قاعدة البيانات أو الإرسال إلى خدمة التحليلات
  console.log('مقياس حيوي:', metric);

  // مثال: الإرسال إلى خدمة خارجية
  if (process.env.ANALYTICS_API_KEY) {
    await fetch('https://analytics.example.com/vitals', {
      method: 'POST',
      headers: {
        'Authorization': \`Bearer ${process.env.ANALYTICS_API_KEY}\`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(metric),
    });
  }

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

علامات الأداء المخصصة

تتبع مقاييس الأداء المخصصة:

lib/performance.ts:
export class PerformanceTracker {
  private marks: Map<string, number> = new Map();

  mark(name: string) {
    if (typeof window !== 'undefined') {
      performance.mark(name);
      this.marks.set(name, performance.now());
    }
  }

  measure(name: string, startMark: string, endMark?: string) {
    if (typeof window !== 'undefined') {
      try {
        performance.measure(name, startMark, endMark);

        const measure = performance.getEntriesByName(name)[0];

        // إرسال إلى التحليلات
        this.sendMetric({
          name,
          duration: measure.duration,
          startTime: measure.startTime,
        });

        return measure.duration;
      } catch (error) {
        console.error('فشل قياس الأداء:', error);
      }
    }
    return 0;
  }

  private sendMetric(metric: any) {
    // إرسال إلى خدمة التحليلات الخاصة بك
    fetch('/api/analytics/performance', {
      method: 'POST',
      body: JSON.stringify(metric),
      keepalive: true,
    });
  }
}

export const performanceTracker = new PerformanceTracker();
مثال على الاستخدام:
'use client';

import { performanceTracker } from '@/lib/performance';
import { useEffect } from 'react';

export default function Dashboard() {
  useEffect(() => {
    performanceTracker.mark('dashboard-render-start');

    // محاكاة تحميل البيانات
    fetchDashboardData().then(() => {
      performanceTracker.mark('dashboard-data-loaded');
      performanceTracker.measure(
        'dashboard-load-time',
        'dashboard-render-start',
        'dashboard-data-loaded'
      );
    });
  }, []);

  return (
    <div className="dashboard">
      {/* محتوى لوحة المعلومات */}
    </div>
  );
}

مراقبة المستخدم الحقيقي (RUM)

تتبع مقاييس تجربة المستخدم الحقيقية:

lib/rum.ts:
interface RUMMetrics {
  url: string;
  userAgent: string;
  connection?: string;
  deviceMemory?: number;
  navigationTiming: {
    dns: number;
    tcp: number;
    request: number;
    response: number;
    domLoading: number;
    domInteractive: number;
    domComplete: number;
    loadComplete: number;
  };
}

export function collectRUMMetrics(): RUMMetrics | null {
  if (typeof window === 'undefined') return null;

  const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;

  if (!nav) return null;

  return {
    url: window.location.href,
    userAgent: navigator.userAgent,
    connection: (navigator as any).connection?.effectiveType,
    deviceMemory: (navigator as any).deviceMemory,
    navigationTiming: {
      dns: nav.domainLookupEnd - nav.domainLookupStart,
      tcp: nav.connectEnd - nav.connectStart,
      request: nav.responseStart - nav.requestStart,
      response: nav.responseEnd - nav.responseStart,
      domLoading: nav.domInteractive - nav.responseEnd,
      domInteractive: nav.domInteractive - nav.navigationStart,
      domComplete: nav.domComplete - nav.navigationStart,
      loadComplete: nav.loadEventEnd - nav.navigationStart,
    },
  };
}

export function sendRUMMetrics() {
  window.addEventListener('load', () => {
    setTimeout(() => {
      const metrics = collectRUMMetrics();

      if (metrics) {
        navigator.sendBeacon(
          '/api/analytics/rum',
          JSON.stringify(metrics)
        );
      }
    }, 0);
  });
}

مثال على لوحة المعلومات للمراقبة

إنشاء لوحة معلومات تحليلات مخصصة:

app/admin/analytics/page.tsx:
import { Suspense } from 'react';
import { getAnalytics } from '@/lib/analytics-data';

export default async function AnalyticsDashboard() {
  const analytics = await getAnalytics({
    range: '7d',
  });

  return (
    <div className="analytics-dashboard">
      <h1>لوحة التحليلات</h1>

      <div className="metrics-grid">
        <MetricCard
          title="مشاهدات الصفحة"
          value={analytics.pageViews}
          change={analytics.pageViewsChange}
        />
        <MetricCard
          title="الزوار الفريدون"
          value={analytics.uniqueVisitors}
          change={analytics.visitorsChange}
        />
        <MetricCard
          title="متوسط مدة الجلسة"
          value={\`${analytics.avgSessionDuration}ث\`}
          change={analytics.sessionChange}
        />
        <MetricCard
          title="معدل الارتداد"
          value={\`${analytics.bounceRate}%\`}
          change={analytics.bounceRateChange}
          inverse
        />
      </div>

      <div className="charts-grid">
        <Suspense fallback={<Loading />}>
          <PageViewsChart data={analytics.pageViewsOverTime} />
        </Suspense>

        <Suspense fallback={<Loading />}>
          <TopPagesTable pages={analytics.topPages} />
        </Suspense>

        <Suspense fallback={<Loading />}>
          <WebVitalsChart vitals={analytics.webVitals} />
        </Suspense>
      </div>
    </div>
  );
}
تمرين:
  1. قم بتثبيت وتكوين Vercel Analytics في مشروع Next.js
  2. أضف تتبع أحداث مخصص لـ 3 تفاعلات رئيسية للمستخدم
  3. قم بإعداد تتبع أخطاء Sentry مع السياق المخصص
  4. أنشئ نظام تتبع أداء مخصص
  5. قم ببناء لوحة معلومات تحليلات بسيطة تعرض المقاييس الرئيسية
أفضل الممارسات:
  • تتبع الأحداث المهمة فقط - تجنب تتبع الضوضاء
  • خذ عينات من التطبيقات ذات الحركة المرورية العالية لتقليل التكاليف
  • قم بإعداد تنبيهات للأخطاء الحرجة وتدهور الأداء
  • احترم خصوصية المستخدم - قم بإخفاء هوية البيانات الحساسة
  • استخدم متغيرات البيئة لجميع مفاتيح API
  • راقب أداء الواجهة الأمامية والخلفية
  • راجع بيانات التحليلات بانتظام واتخذ إجراءات بشأنها