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_keyThe
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.
| 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 |
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()orGalacha.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
| Problem | Fix |
|---|---|
Next.js SSR error about window | Call init from useEffect, not at module scope. |
| 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 . they're always masked. If you're seeing values, you're looking at a text input. |
| Dev-mode double init | React 18 strict mode double-invokes effects. init is idempotent. |