Google Analytics 4 en mi sitio: quién me visita, desde qué país y qué secciones ve

TL;DR: creé una propiedad de GA4 por sitio (la landing y el blog), puse el ID de medición en cada uno y verifiqué que la etiqueta solo sale en el build de producción. En la landing de React agregué un evento seccion_vista para saber no solo cuántos abren la página, sino cuántos llegan al Portafolio, al Proceso o al Equipo. Todo en unos 20 minutos.

Qué quería saber

Tengo dos sitios: la landing de mi empresa (React + Vite) y este blog (Hugo). Quería responder preguntas muy concretas:

  • ¿Cuántas personas entran y cuántas regresan?
  • ¿Desde qué país y ciudad?
  • ¿Llegan por Google, por LinkedIn, por WhatsApp o escribiendo la dirección?
  • En la landing: ¿cuánta gente baja hasta ver mi propuesta (portafolio, proceso, equipo) y cuánta se va en el primer pantallazo?

Las cuatro primeras las responde GA4 solo, sin configurar nada. La última necesita un evento propio.

Flujo: el visitante abre la página, la etiqueta gtag.js manda los datos a la propiedad de GA4 y de ahí salen los informes

Paso 1 · Una propiedad por sitio (no una para todo)

En GA4 la jerarquía es Cuenta → Propiedad → Flujo de datos. Decidí una propiedad por sitio porque:

  • Los informes de cada sitio salen limpios, sin filtrar por dominio cada vez.
  • Puedo dar acceso a alguien solo a uno de los dos.
  • Los objetivos son distintos: la landing busca contactos; el blog, lectura.

El asistente tiene cuatro pasos:

Los cuatro pasos para crear la propiedad: nombre y zona horaria, datos de la empresa, objetivos y flujo web

  1. Nombre, zona horaria y moneda. Nombre descriptivo (Mi empresa (midominio.com)), zona (GMT-06:00) Ciudad de México y peso mexicano. Ojo: si eliges «México», el asistente pone por defecto la hora de Tijuana; cámbiala o tus informes «del día» se cortan dos horas antes.
  2. Datos de la empresa. Sector y tamaño. Solo afectan las comparativas que te sugiere Google.
  3. Objetivos. Para la landing elegí Generar oportunidades de venta + Conocer el tráfico web; para el blog, Conocer el tráfico + Interacción y retención. Cambian los informes que aparecen en el menú, nada más.
  4. Flujo web. URL del sitio y nombre del flujo. Al crearlo te da el ID de medición con forma G-XXXXXXXXXX.

El ID de medición no es un secreto: aparece en el HTML de cualquier sitio que use Analytics. Puede ir en el código sin problema (a diferencia de una API key).

Paso 2 · El blog en Hugo (3 líneas)

Hugo ya trae la plantilla interna de Google Analytics. Si tu layout base la incluye ({{ template "_internal/google_analytics.html" . }}), solo falta el ID en config.toml:

# Google Analytics 4. Hugo solo inserta la etiqueta en el build de producción.
[services]
  [services.googleAnalytics]
    ID = "G-XXXXXXXXXX"

En versiones nuevas de Hugo la clave vieja googleAnalytics = "..." en la raíz ya no se usa: va dentro de [services].

Lo bueno: con hugo server no se inserta, así que tus propias visitas mientras escribes no ensucian las cifras.

Paso 3 · La landing en React + Vite

Aquí no hay plantilla, así que hice un módulo pequeño, src/analitica.js, y lo llamo una vez en main.jsx:

// Google Analytics 4. Solo en producción.
const ID = import.meta.env.VITE_GA_ID || "G-XXXXXXXXXX";

export function iniciarAnalitica() {
  if (!import.meta.env.PROD || !ID) return;          // nada en desarrollo
  const s = document.createElement("script");
  s.async = true;
  s.src = `https://www.googletagmanager.com/gtag/js?id=${ID}`;
  document.head.appendChild(s);
  window.dataLayer = window.dataLayer || [];
  window.gtag = function gtag() {
    window.dataLayer.push(arguments);               // gtag exige el objeto arguments tal cual
  };
  window.gtag("js", new Date());
  window.gtag("config", ID);

  // Evento propio: la primera vez que cada sección entra en pantalla
  const vistas = new Set();
  const io = new IntersectionObserver((entradas) => {
    entradas.forEach((e) => {
      const id = e.target.id;
      if (!e.isIntersecting || vistas.has(id)) return;
      vistas.add(id);
      window.gtag("event", "seccion_vista", { seccion: id });
    });
  }, { threshold: 0.35 });
  // React monta las secciones después: esperar a que existan
  setTimeout(() => document.querySelectorAll("section[id]").forEach((n) => io.observe(n)), 1200);
}
// main.jsx
import { iniciarAnalitica } from "./analitica.js";
iniciarAnalitica();

Tres detalles que importan:

  • import.meta.env.PROD: con npm run dev no se carga nada.
  • arguments y no ...args: gtag espera exactamente el objeto arguments; con un arreglo normal los eventos no llegan.
  • threshold: 0.35: la sección cuenta como «vista» cuando se ve al menos un tercio, no cuando apenas asoma un pixel. Y cada sección se cuenta una sola vez por visita.

Paso 4 · Verificar el efecto, no solo el archivo

Escribir bien el archivo no prueba que funcione. Compilé los dos sitios a una carpeta temporal (para no tocar la de publicación) y busqué la etiqueta en el resultado:

# Blog (Hugo): ¿la etiqueta sale en el HTML?
hugo --quiet -d /tmp/prueba-hugo
grep -o "gtag/js?id=G-[A-Z0-9]*" /tmp/prueba-hugo/index.html     # → la etiqueta con tu ID
grep -l "G-XXXXXXXXXX" /tmp/prueba-hugo/posts/*/index.html | wc -l  # → todos los posts

# Landing (Vite): ¿el ID quedó en el bundle?
npx vite build --outDir /tmp/prueba-vite --emptyOutDir
grep -l "G-XXXXXXXXXX" /tmp/prueba-vite/assets/*.js

En mi caso la etiqueta apareció en la portada y en los 57 posts, y el ID quedó dentro del JavaScript de la landing.

Después de publicar, la prueba final está en Informes → Tiempo real: abre tu sitio desde el teléfono y en menos de un minuto debes verte como «1 usuario», con tu ciudad.

Paso 5 · Ver el evento por sección en los informes

GA4 recibe seccion_vista desde el primer día, pero para agrupar por el nombre de la sección hay que registrar el parámetro una vez:

  1. Administrar → Definiciones personalizadas → Crear dimensión personalizada.
  2. Nombre: Sección · Alcance: Evento · Parámetro: seccion.
  3. En Exploraciones cruza Sección con Usuarios: ahí ves cuánta gente llegó a «portafolio», a «proceso» o a «equipo».

Las dimensiones solo empiezan a llenarse desde que las creas: conviene hacerlo el mismo día que publicas.

Lo que GA4 te da sin configurar nada

Pregunta Dónde verla
¿Cuántas personas entran? Informes → Resumen (Usuarios activos)
¿Desde qué país y ciudad? Informes → Usuario → Atributos demográficos
¿Por dónde llegan? Informes → Adquisición → Adquisición de tráfico
¿Qué páginas leen más? Informes → Interacción → Páginas y pantallas
¿Quién está ahora mismo? Informes → Tiempo real

Antes de publicar: privacidad

Analytics usa cookies. En México, la LFPDPPP pide un aviso de privacidad que diga qué datos recabas y para qué; agrega un párrafo sobre analítica. Si recibes visitas de Europa, el RGPD exige consentimiento previo: ahí conviene un banner con Consent Mode de Google. Para un sitio que apenas empieza, el aviso de privacidad es lo mínimo indispensable.

Resumen

  1. Una propiedad por sitio, con zona horaria de Ciudad de México (no la de Tijuana que pone por defecto).
  2. Hugo: [services.googleAnalytics] ID = "G-..." y listo; solo sale en producción.
  3. React + Vite: un módulo de 30 líneas con gtag, solo en PROD, más un evento seccion_vista con IntersectionObserver.
  4. Verifica compilando a una carpeta temporal y buscando el ID; después, Tiempo real.
  5. Registra la dimensión personalizada seccion el mismo día.
Atajos de una palabra para crear contenido de redes con mi GPU: post, clip, semana
Older post

Atajos de una palabra para crear contenido de redes con mi GPU: post, clip, semana

Cómo organicé la generación de contenido por red social y por proyecto con atajos para mi agente de IA: escribo 'post miproyecto ig: idea' o 'clip miproyecto tiktok: idea' y en segundos tengo imágenes o un video con voz, con el estilo de la marca y en el formato de cada red.

Newer post

Cómo hice la mascota de mi marca con Claude Code y Gemini: del logo a un personaje que te sigue con la mirada

Convertí el logo de WeSoft en Manta, una mantarraya 3D estilo película animada, con Gemini (Nano Banana) manejado por Claude Code. Te enseño los prompts exactos, cómo conseguir 11 tomas idénticas del mismo personaje, cómo recortarlas para la web, hacer que miren al cursor y animarlas en video sin pagar nada.

Cómo hice la mascota de mi marca con Claude Code y Gemini: del logo a un personaje que te sigue con la mirada
raul@wesoft: ~/ficha