React / Next.js
Install via the @galacha/react npm package. Works in React 17+, Next.js 13+ (App Router and Pages Router), Vite, CRA, Remix, TanStack Start.
Install
npm install @galacha/react
# or
pnpm add @galacha/react
# or
yarn add @galacha/reactPeer dependency: react >= 17. Zero runtime dependencies. The package lazy-loads the recorder from sdk.galacha.me at runtime.
Set the project key
Use your framework's env var convention:
| Framework | Variable |
|---|---|
| Next.js | NEXT_PUBLIC_GALACHA_PROJECT_KEY |
| Vite | VITE_GALACHA_PROJECT_KEY |
| CRA | REACT_APP_GALACHA_PROJECT_KEY |
| Expo (React Native Web) | EXPO_PUBLIC_GALACHA_PROJECT_KEY |
# .env.local (Next.js)
NEXT_PUBLIC_GALACHA_PROJECT_KEY=4d4b83bcd00acd4cbc8ab5a7d94bab5abbc31887dbc1bf3fMount the Provider
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>
);
}GalachaProvider is already a client component. You can drop it into a server layout.tsx — Next.js handles the boundary automatically.
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>
);
}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>,
);Remix
// app/root.tsx
import { GalachaProvider } from "@galacha/react";
export default function App() {
return (
<html>
<body>
<GalachaProvider projectKey={window.ENV.GALACHA_PROJECT_KEY}>
<Outlet />
</GalachaProvider>
<Scripts />
</body>
</html>
);
}You'll need to inject ENV via loader + useLoaderData to expose the env var to the client.
Identify the user
Declarative — recommended:
import { useIdentify } from "@galacha/react";
function Shell() {
const { user } = useAuth();
useIdentify(user?.id, { email: user?.email, name: user?.name });
return <Routes />;
}Imperative:
import { useGalacha } from "@galacha/react";
function LoginForm() {
const { identify } = useGalacha();
const handleLogin = async (email, password) => {
const user = await api.login(email, password);
identify(user.id, { email: user.email });
};
}Mask sensitive views
import { PrivateBlock } from "@galacha/react";
<PrivateBlock>
<CreditCardForm />
</PrivateBlock>Block mode (stronger — DOM subtree dropped entirely):
<PrivateBlock block>
<SSNInput />
</PrivateBlock>See PrivateBlock for the full API.
What the package exports
| Export | Purpose |
|---|---|
<GalachaProvider> | Wraps your tree, loads the SDK, calls init() |
useGalacha() | Hook returning { ready, identify, stop, sessionId, visitorId } |
useIdentify() | Declarative identify hook |
<PrivateBlock> | Mark a DOM subtree as private |
GalachaConfig, GalachaTraits (types) | For TypeScript consumers |
What this package does NOT do
- Ship the recorder bundle — it loads
sdk.galacha.me/sdk/latest/galacha.umd.jsat runtime. Browser caching is shared across sites that use Galacha - Support SSR event capture. Recording only runs in the browser
- Expose
track()orflush()— those are React Native features
Common pitfalls
| Problem | Fix |
|---|---|
SSR error about window | Use <GalachaProvider>, not manual useEffect. It's a client component. |
| Env var undefined in the browser | Use the framework prefix: NEXT_PUBLIC_*, VITE_*, REACT_APP_* |
| StrictMode double-boot | Safe. Boot logic is a singleton on window.__galachaBoot. |
useGalacha must be used inside <GalachaProvider> | Wrap a common ancestor — usually your root layout |
Calling stop() on logout breaks re-recording | Skip the stop call. Let the next identify() retag the session. |