← Todos los artículos

Blog

Conversion Tracking en SPA: Por qué tus tags fallan en React, Next.js y Vue

conversion tracking en spaconversion tracking reacttracking single page appconversion tracking next.js

"El tag funciona en todas las demás páginas, pero en la pantalla de agradecimiento no se dispara nada." Si tu sitio está construido con React, Next.js, Vue o cualquier otro framework de SPA, este es uno de los fallos de conversion tracking más comunes. Y es invisible en la vista previa de tu tag management porque esa vista previa funciona de forma distinta a la navegación real de un usuario.

La causa raíz es simple: las SPA no hacen cargas de página reales. El navegador carga el shell una sola vez, y cada "página" posterior es un intercambio de DOM impulsado por JavaScript. Los tags que dependen de la carga de página —que son la mayoría— nunca ven que ocurrió la navegación.

Esta guía explica por qué se rompe el tracking en las SPA, cómo solucionarlo para cada framework principal y cómo verificar que funciona en producción.

1. Por qué el disparo tradicional de tags no funciona en las SPA

En un sitio multipágina tradicional, cada navegación entre páginas dispara una carga completa de página en el navegador. El sistema de tag management (GTM, gtag embebido directamente, Meta Pixel, etc.) se dispara en cada evento DOMContentLoaded o window.onload. Un tag configurado para dispararse en "Todas las páginas" literalmente se dispara en todas las páginas.

En una SPA:

Sitio multipágina SPA
Clic en un enlace → el navegador solicita nuevo HTML → carga completa de página Clic en un enlace → JavaScript intercambia el DOM → la URL cambia vía History API
Cada página dispara DOMContentLoaded DOMContentLoaded se dispara una sola vez (solo en la carga inicial)
El trigger "Todas las páginas" de GTM se dispara en cada navegación El trigger "Todas las páginas" de GTM se dispara solo en la primera carga
El tag de conversión en /thank-you se dispara al cargar la página El tag de conversión en /thank-you nunca se dispara (no ocurre carga de página)

Esto significa que un tag de conversión configurado para dispararse "cuando carga la página de agradecimiento" se dispara exactamente cero veces, porque la página de agradecimiento nunca "carga" en el sentido que entiende el navegador. El usuario navega hacia ella, la URL cambia, el contenido aparece, pero no ocurre ningún evento de carga de página.

2. Los tres enfoques para solucionarlo

Enfoque A: Enviar un pageview virtual al data layer

El método más confiable para configuraciones basadas en GTM. Cuando la ruta de la SPA cambia, envías un evento al data layer. Los triggers de GTM se disparan con ese evento en lugar del trigger integrado de "Page View".

// En cada cambio de ruta, hacer push al dataLayer
dataLayer.push({
  event: 'virtual_pageview',
  page_path: '/thank-you',
  page_title: 'Thank You'
});

En GTM:

  1. Crea un trigger de Custom Event con el nombre de evento virtual_pageview
  2. Configura tus tags de conversión para que se disparen con este trigger, con una condición sobre page_path (por ejemplo, page_path equals /thank-you)

Este enfoque separa la decisión de "cuándo disparar" del framework: el código de tu app envía el evento y GTM se encarga de la lógica del tag.

Enfoque B: Usar el trigger History Change en GTM

GTM tiene un trigger integrado llamado "History Change" que se dispara cada vez que se usa la History API del navegador (que es como las SPA cambian la URL). Puede funcionar sin ningún cambio de código en tu app.

Configuración:

  1. En GTM, crea un trigger de tipo History Change
  2. Agrega condiciones basadas en {{Page Path}} (por ejemplo, Page Path equals /thank-you)
  3. Vincula tus tags de conversión a este trigger

Advertencia: El trigger History Change se dispara en cada cambio de URL, incluyendo la navegación con los botones atrás/adelante. Sin condiciones cuidadosas, tu tag de conversión puede dispararse cuando un usuario vuelve a pasar por la página de agradecimiento sin haber convertido de nuevo. Esta es una causa común de conversiones duplicadas en las SPA. Si tus tags de GTM no se disparan en absoluto, revisa por qué a veces GTM no trackea para un troubleshooting más amplio.

Enfoque C: Disparar el tag directamente en el código de tu app

Para tags embebidos directamente (sin usar GTM), llama a la función de tracking directamente en tu componente o route handler.

// React: disparar en useEffect cuando el componente se monta
useEffect(() => {
  gtag('event', 'conversion', {
    'send_to': 'AW-XXXXXXX/YYYYYYY',
    'value': 100,
    'currency': 'USD'
  });
}, []);
// Next.js App Router: disparar en un client component en la página de agradecimiento
'use client'
import { useEffect } from 'react'

export default function ThankYou() {
  useEffect(() => {
    gtag('event', 'conversion', {
      'send_to': 'AW-XXXXXXX/YYYYYYY',
      'value': 100,
      'currency': 'USD'
    });
  }, []);

  return <h1>Gracias por tu compra</h1>
}

Ventaja: Sin la complejidad de GTM; la conversión se dispara exactamente cuándo y dónde quieres.

Riesgo: Mezclar código de tracking con el código de la aplicación puede hacer que sea más difícil de mantener. Si tienes muchos tags en muchas páginas, GTM suele ser más manejable.

El soporte nativo de GA4 para SPA (Enhanced Measurement)

Antes de construir un tracking personalizado para SPA, revisa si la función integrada de GA4 ya cubre tus necesidades. GA4 tiene una opción "Page changes based on browser history events" dentro de Enhanced Measurement:

Para activarla: Admin → Data Streams → Web → Enhanced Measurement → Page Views → Show advanced settings → activa "Page changes based on browser history events."

Cuando está activada, GA4 dispara automáticamente un evento page_view cada vez que la History API del navegador actualiza la URL, exactamente lo que ocurre en las SPA. Esto funciona sin necesidad de cambios de código ni configuración en GTM.

Advertencia crítica: no cuentes doble. Si usas GTM para enviar eventos page_view en los cambios de ruta Y además tienes activada esta configuración de GA4, cada navegación de la SPA se cuenta dos veces. Elige uno: o el Enhanced Measurement de GA4 maneja los pageviews automáticamente, o lo hace tu configuración de GTM, no ambos. Este es uno de los errores de configuración más comunes en el tracking de SPA.

Limitación: Esto cubre el tracking de pageviews, no el tracking de eventos de conversión. Igual necesitas disparar los eventos específicos de conversión (purchase, sign_up, etc.) vía GTM o código de la app en el momento correcto.

3. Particularidades por framework

React (Create React App, Vite)

  • El problema: React Router cambia la URL vía la History API. No se dispara ninguna carga de página.
  • Solución: Usa useEffect en el componente de destino, o escucha los cambios de ruta en tu router y envía al data layer.
  • Ten cuidado: useEffect se ejecuta después del render, lo que significa que la conversión se dispara un poco después de la actualización del DOM. Normalmente esto no es problema, pero si el usuario abandona la página muy rápido (por ejemplo, un redirect instantáneo), el tag puede no completarse.

Next.js

  • App Router: Las páginas pueden ser renderizadas en el servidor (RSC) o en el cliente. Los tags de conversión deben ejecutarse en el cliente. Usa un componente 'use client' con useEffect, o usa el componente next/script con la estrategia afterInteractive.
  • Pages Router: Usa la API router.events para escuchar routeChangeComplete y enviar al data layer.
  • Exportación estática vs. servidor: Si usas output: 'export', el comportamiento es similar al de una SPA estándar. Si usas server-side rendering, las cargas de página iniciales disparan los tags con normalidad; solo se ven afectadas las navegaciones del lado del cliente.
  • Ten cuidado: Next.js hace prefetch de los enlaces. El prefetch no dispara un cambio de ruta, pero si tu tag se dispara con el evento incorrecto, la actividad de DOM relacionada con el prefetch puede provocar disparos fantasma.

Vue (Nuxt)

  • Vue Router: Escucha router.afterEach() para enviar pageviews virtuales al data layer.
  • Nuxt: Usa el hook page:finish o un plugin que se dispare con los cambios de ruta.
  • Ten cuidado: El sistema de transiciones de Vue puede retrasar las actualizaciones del DOM. Si tu tag verifica la presencia de un elemento del DOM en la página de destino, puede que aún no exista cuando se dispare el evento de cambio de ruta.

Angular

  • Angular Router: Suscríbete a Router.events y filtra los eventos NavigationEnd. Envía al data layer en cada NavigationEnd.
  • Ten cuidado: Zone.js de Angular puede interferir con scripts de terceros. Si un tag se carga de forma asíncrona y se ejecuta fuera de la zona de Angular, puede comportarse de forma impredecible.

4. La trampa de las conversiones duplicadas

Las SPA hacen que las conversiones duplicadas sean más probables que en los sitios multipágina, por dos razones:

  1. Navegación por historial: Un usuario completa una compra, llega a /thank-you, luego presiona el botón atrás y después adelante: el trigger History Change se dispara de nuevo y la conversión se cuenta dos veces.

  2. Remontaje de componentes: En React, si el componente de la página de agradecimiento se desmonta y se vuelve a montar (por un re-render del padre o un parpadeo de ruta), useEffect se ejecuta de nuevo y dispara el tag una segunda vez.

Soluciones:

  • Usa una bandera de una sola vez: establece una variable (session storage, un React ref o una variable del data layer) después del primer disparo, y verifícala antes de volver a disparar
  • En GTM, usa la opción "el trigger se dispara una vez por página", pero ten en cuenta que en una SPA "por página" significa por carga de página inicial, no por cambio de ruta
  • Deduplica del lado de la plataforma: usa el parámetro transaction_id (Google Ads) o eventID (Meta) para que la plataforma ignore las señales duplicadas

5. Higiene del data layer entre navegaciones de la SPA

En un sitio multipágina, cada navegación reinicia el data layer: empieza de cero en cada carga de página. En una SPA, el data layer persiste entre cambios de ruta. Esto crea un problema sutil pero dañino: los valores obsoletos de una página anterior contaminan los eventos de la página actual.

Cómo los valores obsoletos generan datos de conversión incorrectos

Un usuario ve el Producto A ($199), lo agrega al carrito, luego navega al Producto B ($49) y compra. Si el data layer todavía conserva los valores del Producto A cuando se dispara el evento de compra, la conversión puede reportar $199 en lugar de $49, o peor, incluir los datos de artículo del Producto A junto con los del Producto B.

La solución: limpiar antes de cada pageview virtual

Reinicia las variables relevantes del data layer en cada cambio de ruta, antes de enviar el nuevo pageview:

// En el cambio de ruta — limpiar y luego enviar
dataLayer.push({
  event: 'spa_cleanup',
  ecommerce: null  // limpia los datos de ecommerce de GA4
});

dataLayer.push({
  event: 'spa_pageview',
  spa_page_path: newPath,
  spa_page_title: newTitle
});

Establecer ecommerce: null es específicamente lo que recomienda Google para limpiar datos de ecommerce obsoletos entre eventos (consulta nuestra guía sobre eventos de compra de ecommerce en GA4 para ver la estructura completa del data layer). Para las variables que no son de ecommerce, establécelas explícitamente en undefined o envía un nuevo objeto que sobrescriba los valores anteriores.

Qué vigilar

  • El transaction_id de una compra anterior que persiste: hace que el siguiente evento arrastre un transaction_id antiguo, lo que puede deduplicar una conversión nueva real
  • Discrepancia de moneda: un usuario que navega un producto en USD y luego cambia a uno en JPY; si el currency no se vuelve a enviar, persiste el valor anterior
  • Versión de la variable de data layer en GTM: las variables de Versión 1 persisten hasta que se limpian explícitamente; las de Versión 2 están acotadas al evento, pero igual pueden filtrarse si se leen en el momento incorrecto

6. Cómo verificar el conversion tracking en una SPA

Paso 1: Verifica con la pestaña Network de DevTools

Navega a la página de conversión dentro de la SPA (no la recargues; navega desde otra página). En la pestaña Network, filtra la solicitud de conversión:

  • Google Ads: googleadservices.com/pagead/conversion/
  • GA4: google-analytics.com/g/collect
  • Meta: facebook.com/tr

Si la solicitud aparece después de la navegación, el tag se dispara correctamente. Si solo aparece con una recarga completa de página, el tag está configurado únicamente para carga de página y no está capturando la navegación de la SPA.

Paso 2: Prueba el escenario del botón atrás

Después de disparar la conversión, presiona el botón atrás y luego el botón adelante. Revisa la pestaña Network de nuevo: ¿la solicitud de conversión se disparó una segunda vez? Si es así, tienes un problema de conversión duplicada.

Paso 3: Limitaciones del modo de vista previa de GTM

El modo Preview de GTM puede no ser confiable para probar SPA. El panel de vista previa a veces muestra tags como "disparados" incluso cuando la solicitud de red real no salió, o puede no reflejar correctamente los eventos History Change. Siempre haz una verificación cruzada con la pestaña Network, no solo con GTM Preview.

Paso 4: Prueba en un entorno limpio

Las SPA pueden comportarse de forma distinta según:

  • Si se accedió a la página escribiendo la URL directamente (carga completa de página) frente a navegación interna (cambio de ruta de la SPA)
  • Si hay service workers cacheando la página
  • Si el usuario tiene bloqueadores de anuncios que interfieren con el tag

Prueba ambos caminos: una carga directa por URL de la página de agradecimiento, y una navegación hacia ella desde dentro de la app.

7. Server-side tagging para SPA

Las SPA son particularmente vulnerables a los fallos de tracking del lado del cliente: los bloqueadores de anuncios interceptan las solicitudes a googleadservices.com, facebook.com/tr y otros endpoints de tracking; las plataformas de gestión de consentimiento pueden bloquear los tags antes de que se disparen; y el JavaScript pesado del lado del cliente puede retrasar o impedir la ejecución de los tags.

El Google Tag Manager del lado del servidor (sGTM) aborda estos problemas enrutando las solicitudes de tracking a través de tu propio dominio:

Problema Solo del lado del cliente Con GTM del lado del servidor
Bloqueador de anuncios que bloquea las solicitudes de conversión La solicitud se bloquea, se pierde la conversión La solicitud va primero a tu dominio y luego se reenvía del lado del servidor; no se bloquea
Consent Mode v2 en SPA El estado de consentimiento debe reverificarse en cada pageview virtual (no hay recarga de página para reinicializarlo) El contenedor del lado del servidor puede aplicar el estado de consentimiento de forma consistente
Velocidad de ejecución de los tags Se cargan múltiples scripts de terceros en el cliente, ralentizando la SPA Solo una solicitud a tu servidor; el servidor se encarga de distribuirla a múltiples plataformas

Cuándo considerar el server-side tagging para tu SPA:

  • La tasa de bloqueadores de anuncios entre tu audiencia supera el 20-30%
  • Ves una brecha grande entre el conteo de pedidos del lado del servidor y las conversiones reportadas
  • Necesitas cumplir con las regulaciones de privacidad de la UE y quieres un control más estricto sobre qué datos salen del navegador

Nota práctica: El GTM del lado del servidor no reemplaza el tracking de SPA del lado del cliente, lo complementa. Igual necesitas disparar los eventos correctos en el momento correcto en el código de tu SPA; el server-side tagging cambia dónde se procesan esos eventos, no cuándo se disparan.

8. Configuración de GTM para SPA: un ejemplo funcional

Aquí tienes una configuración completa de GTM para trackear conversiones en una SPA:

Push al data layer (en el código de tu app):

// Dispara esto en cada cambio de ruta
function onRouteChange(path, title) {
  window.dataLayer = window.dataLayer || [];
  dataLayer.push({
    event: 'spa_pageview',
    spa_page_path: path,
    spa_page_title: title
  });
}

Trigger de GTM:

  • Tipo: Custom Event
  • Nombre del evento: spa_pageview
  • Este trigger se dispara en: Some Custom Events
  • Condición: spa_page_path equals /thank-you

Tag de GTM:

  • Tipo: Google Ads Conversion Tracking (o GA4 Event, Meta Pixel, etc.)
  • Trigger: el trigger de conversión de SPA anterior

Esto mantiene la lógica de tracking en GTM (fácil de mantener) mientras se apoya en tu app para señalar cuándo ocurren las navegaciones (confiable en las SPA).

Preguntas frecuentes

P. ¿Necesito cambiar mi configuración de GTM si migro de un sitio multipágina a una SPA? R. Sí. Cualquier tag que se dispare con el trigger integrado "Todas las páginas" o "Page View" dejará de dispararse en las navegaciones de la SPA. Necesitas agregar triggers de History Change o triggers de eventos de data layer para cada tag que deba dispararse en los cambios de ruta.

P. Mi conversión de envío de formulario no se trackea en mi SPA. ¿Qué es diferente? R. En las SPA, los envíos de formulario suelen ocurrir vía JavaScript (AJAX/fetch) sin ninguna navegación de página, así que ni un trigger de carga de página ni un trigger History Change los capturarán. Necesitas disparar el evento de conversión directamente en el success handler del formulario, o enviar un evento al data layer cuando se complete el envío. Para más sobre este patrón, consulta por qué los envíos de formulario no se están trackeando.

P. Mi tag de conversión se dispara con la recarga completa de página pero no con la navegación de la SPA. ¿Hay una solución rápida? R. La solución más rápida es el trigger History Change de GTM, sin cambios de código. Pero para mayor confiabilidad, enviar un evento personalizado al data layer desde el código de tu app es mejor, porque controlas exactamente cuándo se dispara el evento.

P. ¿El gtag.js de Google maneja automáticamente la navegación de la SPA? R. El Google tag (gtag.js) sí tiene cierto soporte para SPA: puede detectar cambios de la History API y enviar eventos de pageview. Pero los eventos de conversión (como gtag('event', 'conversion', ...)) todavía deben llamarse explícitamente en el momento correcto. No confíes en la detección automática para el conversion tracking.

P. Usamos server-side rendering (SSR). ¿Esto igual aplica? R. Parcialmente. La carga de página inicial es una carga de página real (el SSR envía el HTML completo), así que los tags se disparan con normalidad en el primer hit. Pero las navegaciones posteriores dentro de la app siguen siendo navegaciones de SPA del lado del cliente, y esas necesitan el tratamiento de SPA. En Next.js, esto significa que la primera carga de página funciona bien, pero hacer clic en un <Link> hacia otra página es una transición de SPA.

Conclusión: el tag no está roto, simplemente nunca vio la carga de página

Los fallos de conversion tracking en SPA no se deben a tags rotos. Los tags están bien; simplemente están escuchando un evento (la carga de página) que nunca ocurre después de la carga inicial. La solución siempre sigue el mismo patrón: avisarle al tag cuándo ocurrió una navegación, ya sea vía un push al data layer, un listener de History Change, o una llamada directa a una función en el código de tu componente.

El problema más sutil es la verificación. Usa nuestra checklist de verificación de conversion tracking como punto de partida, pero agrega verificaciones específicas para SPA. Como las SPA se comportan de forma distinta según cómo llegó el usuario a la página, una conversión puede funcionar en un escenario y fallar en otro, y GTM Preview no siempre detecta la diferencia. Verifica siempre con la pestaña Network, y prueba siempre tanto el acceso directo por URL como la navegación dentro de la app.

ConversionOK prueba tus páginas en vivo en un navegador independiente y verifica si las señales de conversión realmente se envían, sin importar cómo se construyó la página. Para las SPA, esto detecta los casos en los que un tag está instalado pero nunca se dispara en la página de conversión, un problema que es invisible hasta que lo miras a nivel de red.