> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scanova.io/llms.txt
> Use this file to discover all available pages before exploring further.

# إعداد تطبيقات SPA وأطر العمل

> ثبّت SDK المتصفح من Scanova في React وNext.js وVue وغيرها من تطبيقات الصفحة الواحدة: تجنّب التهيئة المزدوجة وتتبّع مشاهدات الصفحة عند تغيّر المسار.

تختلف دورة حياة الصفحة في تطبيقات الصفحة الواحدة (SPA) عن المواقع التقليدية متعددة الصفحات. فالمتصفح لا يعيد التحميل بالكامل عند التنقل، ولذلك يُطلَق `autoPageview` المعتاد مرة واحدة فقط — عند التحميل الأول. يوضح هذا الدليل كيفية التعامل مع التتبع بشكل صحيح في React وNext.js وVue.

## الفرق الجوهري في تطبيقات SPA

في الموقع التقليدي، يؤدي كل تنقّل إلى إعادة تحميل كاملة تعيد تشغيل SDK وتُطلق حدث `pageview`. أما في تطبيق SPA:

* تُحمَّل الصفحة **مرة واحدة**
* تتم عمليات التنقّل اللاحقة عبر JavaScript (يتغيّر الرابط دون إعادة تحميل الصفحة)
* يُطلَق `autoPageview` عند ذلك التحميل الأول فقط
* **عليك إطلاق حدث `pageview` يدويًا عند كل تغيّر للمسار**

## قواعد عامة لكل تطبيقات SPA

1. **حمّل نص SDK مرة واحدة** — في ملف HTML الجذر أو المكوّن الجذر، لا في كل مكوّن صفحة أو مسار
2. **استدعِ `scanova('init', ...)` مرة واحدة** — في نقطة دخول تطبيقك
3. **عطّل `autoPageview`** — وتولَّ بنفسك أحداث مشاهدة الصفحة عند تغيّر المسار
4. **أطلق `scanova('track', 'pageview')` عند كل تغيّر للمسار** — باستخدام حدث التنقّل أو الخطّاف الخاص بالموجّه لديك

***

## React (Create React App / Vite)

**`public/index.html`** — لتحميل SDK:

```html theme={null}
<head>
  <script>
  (function(w,d,s,o,f,js,fjs){
    w['ScanovaTrackingObject']=o;w[o]=w[o]||function(){(w[o].q=w[o].q||[]).push(arguments)};
    js=d.createElement(s),fjs=d.getElementsByTagName(s)[0];
    js.id=o;js.src=f;js.async=1;fjs.parentNode.insertBefore(js,fjs);
  })(window,document,'script','scanova','https://cdn.scanova.io/ct/js/qcg.min.js');
  </script>
</head>
```

**`src/main.jsx`** أو **`src/App.jsx`** — للتهيئة مرة واحدة وتتبّع تغيّرات المسار:

```jsx theme={null}
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

// Init once at app boot (outside any component)
window.scanova?.('init', 'YOUR_SITE_ID', {
  autoPageview: false,   // manage manually
  autoClicks: true,
  autoForms: true,
  autoScroll: true,
});

// Hook to track route changes
function usePageTracking() {
  const location = useLocation();

  useEffect(() => {
    window.scanova?.('track', 'pageview', {
      path: location.pathname,
    });
  }, [location.pathname]);
}

export default function App() {
  usePageTracking();
  return <RouterOutlet />;
}
```

***

## Next.js (App Router)

في App Router الخاص بـ Next.js، استخدم مكوّن عميل داخل التخطيط الجذر لديك.

**`app/layout.tsx`:**

```tsx theme={null}
import ScanovaTracker from '@/components/ScanovaTracker';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <head>
        <script
          dangerouslySetInnerHTML={{
            __html: `
              (function(w,d,s,o,f,js,fjs){
                w['ScanovaTrackingObject']=o;w[o]=w[o]||function(){(w[o].q=w[o].q||[]).push(arguments)};
                js=d.createElement(s),fjs=d.getElementsByTagName(s)[0];
                js.id=o;js.src=f;js.async=1;fjs.parentNode.insertBefore(js,fjs);
              })(window,document,'script','scanova','https://cdn.scanova.io/ct/js/qcg.min.js');
              scanova('init', 'YOUR_SITE_ID', { autoPageview: false, autoClicks: true, autoForms: true, autoScroll: true });
            `,
          }}
        />
      </head>
      <body>
        <ScanovaTracker />
        {children}
      </body>
    </html>
  );
}
```

**`components/ScanovaTracker.tsx`:**

```tsx theme={null}
'use client';

import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';

export default function ScanovaTracker() {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    window.scanova?.('track', 'pageview', {
      path: pathname,
    });
  }, [pathname, searchParams]);

  return null;
}
```

***

## Next.js (Pages Router)

**`pages/_app.tsx`:**

```tsx theme={null}
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  const router = useRouter();

  useEffect(() => {
    // Init once
    (window as any).scanova?.('init', 'YOUR_SITE_ID', {
      autoPageview: false,
      autoClicks: true,
      autoForms: true,
      autoScroll: true,
    });

    // Track initial page
    (window as any).scanova?.('track', 'pageview', { path: router.pathname });

    // Track subsequent navigations
    const handleRouteChange = (url: string) => {
      (window as any).scanova?.('track', 'pageview', { path: url });
    };

    router.events.on('routeChangeComplete', handleRouteChange);
    return () => router.events.off('routeChangeComplete', handleRouteChange);
  }, []);

  return <Component {...pageProps} />;
}
```

أضف مُحمِّل SDK إلى **`pages/_document.tsx`**:

```tsx theme={null}
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html>
      <Head>
        <script dangerouslySetInnerHTML={{ __html: `
          (function(w,d,s,o,f,js,fjs){
            w['ScanovaTrackingObject']=o;w[o]=w[o]||function(){(w[o].q=w[o].q||[]).push(arguments)};
            js=d.createElement(s),fjs=d.getElementsByTagName(s)[0];
            js.id=o;js.src=f;js.async=1;fjs.parentNode.insertBefore(js,fjs);
          })(window,document,'script','scanova','https://cdn.scanova.io/ct/js/qcg.min.js');
        `}} />
      </Head>
      <body><Main /><NextScript /></body>
    </Html>
  );
}
```

***

## Vue 3 (Vue Router)

**`index.html`** — لتحميل SDK:

```html theme={null}
<head>
  <script>
  (function(w,d,s,o,f,js,fjs){
    w['ScanovaTrackingObject']=o;w[o]=w[o]||function(){(w[o].q=w[o].q||[]).push(arguments)};
    js=d.createElement(s),fjs=d.getElementsByTagName(s)[0];
    js.id=o;js.src=f;js.async=1;fjs.parentNode.insertBefore(js,fjs);
  })(window,document,'script','scanova','https://cdn.scanova.io/ct/js/qcg.min.js');
  </script>
</head>
```

**`src/main.ts`:**

```typescript theme={null}
import { createApp } from 'vue';
import { createRouter } from 'vue-router';
import App from './App.vue';

const router = createRouter({ ... });

// Init once
window.scanova?.('init', 'YOUR_SITE_ID', {
  autoPageview: false,
  autoClicks: true,
  autoForms: true,
  autoScroll: true,
});

// Track route changes
router.afterEach((to) => {
  window.scanova?.('track', 'pageview', { path: to.path });
});

createApp(App).use(router).mount('#app');
```

***

## أخطاء شائعة

| الخطأ                                                     | النتيجة                                                                                         | الحل                                                                                                       |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| استدعاء `scanova('init', ...)` في كل مكوّن صفحة أو مسار   | استدعاءات init متعددة ← مستمعو تتبع مكرّرون ← أحداث مضاعفة                                      | استدعِ `init` مرة واحدة فقط في جذر التطبيق                                                                 |
| استخدام `autoPageview: true` في تطبيق SPA                 | يُطلَق عند التحميل الأول فقط ويفوّت كل عمليات التنقّل                                           | عطّله وتتبّع يدويًا عند تغيّر المسار                                                                       |
| إضافة نص SDK إلى كل مكوّن صفحة                            | يُحمَّل SDK عدة مرات                                                                            | حمّله مرة واحدة من HTML الجذر أو التخطيط الجذر                                                             |
| استدعاء `track` قبل اكتمال تحميل SDK                      | إخفاقات صامتة                                                                                   | استخدم التسلسل الاختياري `window.scanova?.()`، أو نمط الطابور (تُصفّ الاستدعاءات السابقة للتحميل تلقائيًا) |
| استخدام `autoScroll: true` مع توقّع علامات تمرير لكل مسار | لا تُعاد تهيئة العلامات عند تغيّر المسار — فمن بلغ 75% في `/home` لن يعيد إطلاقها في `/pricing` | عطّل `autoScroll` في تطبيقات SPA وأرسل أحداث `scroll` يدويًا إن كان عمق التمرير لكل مسار مهمًا             |
