> ## 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.

# Configuración para SPA y frameworks

> Instala el SDK de navegador de Scanova en React, Next.js, Vue y otras SPA: evita la doble inicialización y registra las vistas al cambiar de ruta correctamente.

Las aplicaciones de una sola página (SPA) tienen un ciclo de vida distinto al de los sitios multipágina. El navegador no recarga del todo al navegar, así que el `autoPageview` estándar solo se dispara una vez, en la carga inicial. Esta guía explica cómo hacerlo bien en React, Next.js y Vue.

## La diferencia clave en una SPA

En un sitio tradicional, cada navegación provoca una recarga completa que vuelve a ejecutar el SDK y dispara un `pageview`. En una SPA:

* La página se carga **una vez**
* Las navegaciones posteriores las gestiona JavaScript (la URL cambia, pero la página no se recarga)
* `autoPageview` solo se dispara en esa primera carga
* **Debes disparar manualmente un evento `pageview` en cada cambio de ruta**

## Reglas generales para cualquier SPA

1. **Carga el snippet del SDK una sola vez**, en tu HTML raíz o componente raíz, no en cada componente de página o ruta
2. **Llama a `scanova('init', ...)` una sola vez**, en el punto de entrada de tu aplicación
3. **Desactiva `autoPageview`** y gestiona tú los eventos de vista de página al cambiar de ruta
4. **Dispara `scanova('track', 'pageview')` en cada cambio de ruta**, con el evento o hook de navegación de tu router

***

## React (Create React App / Vite)

**`public/index.html`**: carga el 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`** o **`src/App.jsx`**: inicializa una vez y registra los cambios de ruta:

```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)

En el App Router de Next.js, usa un componente de cliente en tu layout raíz.

**`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} />;
}
```

Añade el cargador del SDK a **`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`**: carga el 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');
```

***

## Errores habituales

| Error                                                               | Resultado                                                                                                          | Solución                                                                                                                 |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| Llamar a `scanova('init', ...)` en cada componente de página o ruta | Varias llamadas a init → escuchadores duplicados → eventos por duplicado                                           | Llama a `init` una sola vez en la raíz de la aplicación                                                                  |
| Usar `autoPageview: true` en una SPA                                | Solo se dispara en la primera carga y pierde todas las navegaciones                                                | Desactívalo y registra manualmente al cambiar de ruta                                                                    |
| Añadir el script del SDK a cada componente de página                | El SDK se carga varias veces                                                                                       | Cárgalo una vez desde el HTML o el layout raíz                                                                           |
| Llamar a `track` sin esperar a que el SDK cargue                    | Fallos silenciosos                                                                                                 | Usa encadenamiento opcional `window.scanova?.()`, o el patrón de cola (las llamadas previas a la carga se encolan solas) |
| Usar `autoScroll: true` y esperar hitos de desplazamiento por ruta  | Los hitos no se reinician al cambiar de ruta: quien llegó al 75 % en `/home` no volverá a activarlos en `/pricing` | Desactiva `autoScroll` en SPA y envía eventos `scroll` manualmente si necesitas la profundidad por ruta                  |
