Web

Two ways to install on the web, depending on your stack:

StackInstallWhy
React / Next.jsnpm install @galacha/reactHooks, Provider, <PrivateBlock>, works in App Router
Plain HTML, Vite, Vue, Svelte, anything else<script> tag from CDNOne line, no build step

Both paths load the same underlying recorder (sdk.galacha.me/sdk/latest/galacha.umd.js). The React package is a thin wrapper: Provider mounts, hook exposes identify / stop / state, no extra bytes over the raw script.

Install — CDN script tag

<script src="https://sdk.galacha.me/sdk/latest/galacha.umd.js"></script>

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

Install — @galacha/react (npm)

npm install @galacha/react
# or: pnpm add @galacha/react
# or: yarn add @galacha/react

Wrap your app in <GalachaProvider>:

import { GalachaProvider } from "@galacha/react";
 
<GalachaProvider projectKey={process.env.NEXT_PUBLIC_GALACHA_PROJECT_KEY!}>
  {children}
</GalachaProvider>

That's it. The Provider lazy-loads the recorder from the CDN on mount, calls init(), and exposes useGalacha() / useIdentify() / <PrivateBlock> to the rest of your tree. Full API reference in the @galacha/react section below.

If you need the bundle vendored in your own pipeline (air-gapped network, strict CSP, offline CDN), email kelvin@galacha.me for a 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/sdk/latest/galacha.umd.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

// app/layout.tsx
import { GalachaProvider } from "@galacha/react";
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <GalachaProvider projectKey={process.env.NEXT_PUBLIC_GALACHA_PROJECT_KEY!}>
          {children}
        </GalachaProvider>
      </body>
    </html>
  );
}
# .env.local
NEXT_PUBLIC_GALACHA_PROJECT_KEY=your_project_key

The NEXT_PUBLIC_ prefix is required for Next.js to expose the variable to the browser. GalachaProvider is already a client component, safe to drop into a server layout.tsx.

Next.js . Pages Router

// pages/_app.tsx
import { GalachaProvider } from "@galacha/react";
import type { AppProps } from "next/app";
 
export default function App({ Component, pageProps }: AppProps) {
  return (
    <GalachaProvider projectKey={process.env.NEXT_PUBLIC_GALACHA_PROJECT_KEY!}>
      <Component {...pageProps} />
    </GalachaProvider>
  );
}

React (Vite, CRA)

// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { GalachaProvider } from "@galacha/react";
import App from "./App";
 
createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <GalachaProvider projectKey={import.meta.env.VITE_GALACHA_PROJECT_KEY}>
      <App />
    </GalachaProvider>
  </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/sdk/latest/galacha.umd.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/sdk/latest/galacha.umd.js";
    s.async = true;
    s.onload = () => (window as any).Galacha?.init({ projectKey });
    document.head.appendChild(s);
  }
}
 
export const load = () => ({});

@galacha/react API

Everything the package exports. Peer dep react >= 17. Works in Vite, CRA, Next.js App Router + Pages Router, Remix, TanStack Start.

<GalachaProvider>

Wraps your tree, lazy-loads the recorder from the CDN, calls Galacha.init() exactly once per page load. Safe under React StrictMode, HMR, and multiple mounts.

import { GalachaProvider } from "@galacha/react";
 
<GalachaProvider
  projectKey="your_project_key"
  // Everything below is optional
  debug={false}
  disabled={process.env.NODE_ENV === "test"}
  maskInputs={true}
  captureConsole={true}
  captureNetwork={true}
  captureErrors={true}
  flushIntervalMs={5000}
  flushMaxEvents={50}
  sessionTimeoutMs={30 * 60 * 1000}
>
  {children}
</GalachaProvider>
PropTypeDefaultPurpose
projectKeystringrequiredToken from the dashboard
disabledbooleanfalseSkip loading the SDK entirely (test envs)
debugbooleanfalseVerbose boot logging to the console
sdkUrlstringCDN defaultOverride the recorder URL

All other config fields map 1:1 to the web SDK options.

useGalacha()

import { useGalacha } from "@galacha/react";
 
function StatusBar() {
  const { ready, sessionId, visitorId, identify, stop } = useGalacha();
  return (
    <span>
      {ready ? "recording" : "loading"} . session {sessionId ?? "-"}
    </span>
  );
}

Returns:

FieldTypePurpose
readybooleantrue once the recorder has booted
sessionIdstring | nullCurrent session ID
visitorIdstring | nullPersistent anonymous visitor ID
identify(userId, traits?)functionAttach user identity. Buffered if called before ready
stop()functionTear down recording

useIdentify(userId, traits)

Declarative identify. Runs whenever userId changes. Safe with null.

import { useIdentify } from "@galacha/react";
 
function AppShell() {
  const { user } = useAuth();
  useIdentify(user?.id, { email: user?.email, name: user?.name });
  return <Routes />;
}

<PrivateBlock>

Marks a subtree as private. The recorder masks all inputs, textareas, and text inside:

import { PrivateBlock } from "@galacha/react";
 
<PrivateBlock>
  <CreditCardForm />
</PrivateBlock>

Pass block for a stronger guarantee (the subtree is dropped from the DOM snapshot, not just masked):

<PrivateBlock block>
  <SSNInput />
</PrivateBlock>

After init . identify, stop (plain script users)

If you load the CDN <script> directly (no @galacha/react), call these from window.Galacha:

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
// 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, 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 windowUse <GalachaProvider>, not manual useEffect. It's a client component by default.
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, always masked. If you see values, it's a type="text" field.
stop() on logout means no recording until reloadDon't call stop() on logout. Let the next identify() re-tag the session.
Dev-mode double init@galacha/react is StrictMode-safe. Script tag path is idempotent.