Web
Two ways to install on the web, depending on your stack:
| Stack | Install | Why |
|---|---|---|
| React / Next.js | npm install @galacha/react | Hooks, Provider, <PrivateBlock>, works in App Router |
| Plain HTML, Vite, Vue, Svelte, anything else | <script> tag from CDN | One 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/reactWrap 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_keyThe
NEXT_PUBLIC_prefix is required for Next.js to expose the variable to the browser.GalachaProvideris already a client component, safe to drop into a serverlayout.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>| Prop | Type | Default | Purpose |
|---|---|---|---|
projectKey | string | required | Token from the dashboard |
disabled | boolean | false | Skip loading the SDK entirely (test envs) |
debug | boolean | false | Verbose boot logging to the console |
sdkUrl | string | CDN default | Override 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:
| Field | Type | Purpose |
|---|---|---|
ready | boolean | true once the recorder has booted |
sessionId | string | null | Current session ID |
visitorId | string | null | Persistent anonymous visitor ID |
identify(userId, traits?) | function | Attach user identity. Buffered if called before ready |
stop() | function | Tear 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:
| Method | Signature | Purpose |
|---|---|---|
Galacha.init | (config) => void | Start recording. Idempotent |
Galacha.identify | (userId, traits?) => void | Attach a user identity to the current + future sessions |
Galacha.stop | () => void | Stop 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()orGalacha.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
| Problem | Fix |
|---|---|
Next.js SSR error about window | Use <GalachaProvider>, not manual useEffect. It's a client component by default. |
| Env var undefined in the browser | Use NEXT_PUBLIC_* for Next.js, VITE_* for Vite, EXPO_PUBLIC_* for Expo. |
| Strict CSP blocks the script | Allow https://sdk.galacha.me in script-src and https://api.galacha.me in connect-src. |
| Password inputs visible in replay | They never are, always masked. If you see values, it's a type="text" field. |
stop() on logout means no recording until reload | Don'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. |