Web

The web SDK is framework-agnostic. Plain HTML, Next.js, Vite, Vue, Svelte . same bundle, same Galacha.init(), different lifecycle hook depending on your framework. There's no React-specific wrapper because there doesn't need to be one.

Install

Load the bundle from the CDN:

<script src="https://sdk.galacha.me/v1/galacha.min.js"></script>

The script is ~28KB gzipped, loads async, and exposes a global Galacha object with init, identify, track, stop, flush.

There is no @galacha/sdk, @galacha/react, @galacha/vue, or @galacha/nextjs npm package. If you need the bundle in your own build pipeline (air-gapped network, offline CDN, strict CSP), email kelvin@galacha.me for a vendored drop.

Configuration

Galacha.init({
  projectKey: "your_project_key_from_the_dashboard",
 
  // Optional . defaults shown
  apiUrl: "https://api.galacha.me",
  maskInputs: true,
  captureConsole: true,
  captureNetwork: true,
  captureErrors: true,
 
  flushIntervalMs: 5000,
  flushMaxEvents: 50,
  sessionTimeoutMs: 30 * 60 * 1000,
});

Full option list in the configuration reference. If an option isn't there, it isn't in the SDK . don't pass it.

The project key is the long hex string on your project page in the dashboard . not the display name, not the UUID. It's what the SDK authenticates with.


Copy-paste by framework

Plain HTML

<!DOCTYPE html>
<html>
  <head>
    <script src="https://sdk.galacha.me/v1/galacha.min.js"></script>
    <script>
      Galacha.init({ projectKey: "your_project_key" });
    </script>
  </head>
  <body>
    <!-- your page -->
  </body>
</html>

Reload, interact for a few seconds, check the dashboard.

Next.js . App Router

Create a client component that calls init from a useEffect. Never from a server component . window doesn't exist there.

// app/galacha.tsx
"use client";
 
import { useEffect } from "react";
 
declare global {
  interface Window {
    Galacha?: { init: (opts: { projectKey: string }) => void };
  }
}
 
export function Galacha() {
  useEffect(() => {
    const projectKey = process.env.NEXT_PUBLIC_GALACHA_PROJECT_KEY;
    if (!projectKey) return;
 
    if (!window.Galacha) {
      const s = document.createElement("script");
      s.src = "https://sdk.galacha.me/v1/galacha.min.js";
      s.async = true;
      s.onload = () => window.Galacha?.init({ projectKey });
      document.head.appendChild(s);
    } else {
      window.Galacha.init({ projectKey });
    }
  }, []);
 
  return null;
}
// app/layout.tsx
import { Galacha } from "./galacha";
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Galacha />
        {children}
      </body>
    </html>
  );
}
# .env.local
NEXT_PUBLIC_GALACHA_PROJECT_KEY=your_project_key

The NEXT_PUBLIC_ prefix is required. Without it the variable isn't exposed to the browser.

Next.js . Pages Router

// pages/_app.tsx
import Script from "next/script";
import type { AppProps } from "next/app";
 
export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Script
        src="https://sdk.galacha.me/v1/galacha.min.js"
        strategy="afterInteractive"
        onLoad={() => {
          window.Galacha?.init({
            projectKey: process.env.NEXT_PUBLIC_GALACHA_PROJECT_KEY!,
          });
        }}
      />
      <Component {...pageProps} />
    </>
  );
}

React (Vite, CRA)

// src/main.tsx
import { StrictMode, useEffect } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
 
function Root() {
  useEffect(() => {
    const projectKey = import.meta.env.VITE_GALACHA_PROJECT_KEY;
    if (!projectKey) return;
    const s = document.createElement("script");
    s.src = "https://sdk.galacha.me/v1/galacha.min.js";
    s.async = true;
    s.onload = () => (window as any).Galacha?.init({ projectKey });
    document.head.appendChild(s);
  }, []);
  return <App />;
}
 
createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <Root />
  </StrictMode>,
);

Vue 3

// src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
 
const projectKey = import.meta.env.VITE_GALACHA_PROJECT_KEY;
if (projectKey) {
  const s = document.createElement("script");
  s.src = "https://sdk.galacha.me/v1/galacha.min.js";
  s.async = true;
  s.onload = () => (window as any).Galacha?.init({ projectKey });
  document.head.appendChild(s);
}
 
createApp(App).mount("#app");

SvelteKit

// src/routes/+layout.ts
import { browser } from "$app/environment";
 
if (browser) {
  const projectKey = import.meta.env.VITE_GALACHA_PROJECT_KEY;
  if (projectKey) {
    const s = document.createElement("script");
    s.src = "https://sdk.galacha.me/v1/galacha.min.js";
    s.async = true;
    s.onload = () => (window as any).Galacha?.init({ projectKey });
    document.head.appendChild(s);
  }
}
 
export const load = () => ({});

After init . identify, stop

The web SDK exposes a short public surface. Three functions, all accessed through the window.Galacha global after the CDN script loads.

MethodSignaturePurpose
Galacha.init(config) => voidStart recording. Idempotent
Galacha.identify(userId, traits?) => voidAttach a user identity to the current + future sessions
Galacha.stop() => voidStop recording and flush the buffer

identify takes two positional arguments: a string user ID, and an optional traits object.

// After login
Galacha.identify(String(user.id), {
  email: user.email,
  name: user.name,
  plan: user.plan,
});

There is no Galacha.track() or Galacha.flush() on web. Custom events and manual flushes exist on React Native only. On web, the SDK auto-captures everything unhandled . errors, clicks, navigation, network calls . so you rarely need a manual hook.

Route changes

The SDK patches history.pushState / history.replaceState and listens to popstate, so Next.js, React Router, Vue Router, and SvelteKit route changes are captured automatically. No extra setup.

Common pitfalls

ProblemFix
Next.js SSR error about windowCall init from useEffect, not at module scope.
Env var undefined in the browserUse NEXT_PUBLIC_* for Next.js, VITE_* for Vite, EXPO_PUBLIC_* for Expo.
Strict CSP blocks the scriptAllow https://sdk.galacha.me in script-src and https://api.galacha.me in connect-src.
Password inputs visible in replayThey never are . they're always masked. If you're seeing values, you're looking at a text input.
Dev-mode double initReact 18 strict mode double-invokes effects. init is idempotent.