React

Vite y CRA, tipado de window.growthlab y enrutado SPA.

Para una aplicación React sin framework (Vite, CRA, Remix en modo SPA), la etiqueta va en el HTML que sirve la aplicación, no dentro del árbol de componentes.

Vite

Edita index.html:

<!doctype html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <script
      src="https://cdn.growthlab.ecomlabs.dev/sdk/v1/bootstrap.js"
      data-site-id="site_TU_ID_PUBLICO"
      async
    ></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Ponerla en el <head> es lo correcto aunque la aplicación se renderice después: el SDK espera al DOM y aplica la variante en cuanto los elementos existen.

Create React App

Mismo cambio en public/index.html.

Por qué no cargarlo desde un useEffect

Es tentador hacer esto:

// No hagas esto
useEffect(() => {
  const script = document.createElement('script');
  script.src = 'https://cdn.growthlab.ecomlabs.dev/sdk/v1/bootstrap.js';
  document.head.appendChild(script);
}, []);

Funciona, pero el SDK arranca después del primer render, así que el visitante ve el control y luego la variante aparecer. Ese parpadeo es exactamente lo que el anti-flicker existe para evitar, y desde un efecto llega demasiado tarde para hacerlo.

Tipado

El SDK expone su API en window.GrowthLab. Declárala una vez:

// src/growthlab.d.ts
declare global {
  interface Window {
    GrowthLab?: {
      track(name: string, properties?: Record<string, unknown>): void;
      conversion(
        name: string,
        payload?: { revenue?: number; currency?: string; orderId?: string },
      ): void;
      identify(reference: string, traits?: Record<string, unknown>): void;
      feature(key: string): unknown;
      setConsent(consent: { analytics?: boolean; replay?: boolean }): void;
      getVariant(experimentKey: string): string | null;
    };
  }
}

export {};

Todas las llamadas son seguras antes de que el SDK termine de cargar: el bootstrap instala una cola y reproduce lo encolado cuando el módulo correspondiente está listo. Aun así, el operador ?. evita un error si el script fue bloqueado por un ad blocker.

Enrutado por cliente

React Router, TanStack Router y equivalentes cambian la URL con history.pushState. El SDK observa esa API y re-resuelve los experimentos en cada ruta sin intervención.