SDK
React analytics
Record page views and events from a React app, and tell Myna which content entry each page is showing.
@myna-sh/react/analytics is a thin layer over @myna-sh/sdk/analytics: a provider, a hook to reach the client, and two hooks for the things a React application gets wrong on its own, which are page views on client-side navigation and telling Myna which content entry a page is showing.
$ npm install @myna-sh/react @myna-sh/sdk
import { AnalyticsProvider } from "@myna-sh/react/analytics";
<AnalyticsProvider
ingestKey="myna_ik_..."
environment="production"
identify={user ? { id: user.id, email: user.email, signature } : undefined}
>
<App />
</AnalyticsProvider>
signature comes from your own backend, out of signIdentity in @myna-sh/sdk/feedback/server. Without it the visitor stays anonymous.
Recording events
import { useAnalytics } from "@myna-sh/react/analytics";
function PricingCta() {
const analytics = useAnalytics();
return (
<button onClick={() => analytics.track("signup_clicked", { plan: "pro" })}>
Start building
</button>
);
}
useAnalytics throws outside a provider rather than returning a no-op. A tracking call that silently does nothing is the bug that surfaces a month later, when somebody asks why a funnel has no second step.
Page views
import { usePageview } from "@myna-sh/react/analytics";
function App() {
const { pathname } = useLocation();
usePageview(pathname);
return <Routes />;
}
Your router knows when a route actually resolved. The History API only knows when the URL changed, which is earlier and is sometimes wrong: a navigation that suspends, redirects, or fails would still have been counted.
The hook is guarded against React's development StrictMode double-effect, so a page view is recorded once per navigation rather than twice.
Use usePageview or the provider's autoPageviews, never both. Two sources double every number on the dashboard, and nothing in the data says which half is spurious.
Content context
import { useContentContext } from "@myna-sh/react/analytics";
import { useMynaEntry } from "@myna-sh/react";
function PricingPage() {
const { data: page } = useMynaEntry("pages", "pricing");
useContentContext(page && { entry: page.id, collection: "pages", revision: page.revisionId });
return <Pricing content={page} />;
}
Every event fired while this component is mounted carries the entry, the revision the visitor actually saw, and the release it shipped in. That is what makes release impact answerable without a data pipeline.
The hook clears the content on unmount, so an event on the next route is not attributed to the entry the last one was showing.
Provider options
| Prop | Means |
|---|---|
ingestKey |
Required. A publishable myna_ik_… key. |
apiUrl |
Override for a self-hosted or staging deployment. |
environment |
Your environment name, attached to every event. |
appVersion |
Your build: a version, a commit sha, a deployment id. |
identify |
The signed identity, or undefined when nobody is signed in. |
autoPageviews |
Send $pageview on load and on History navigation. |
storage |
local, session, or memory. |
onError |
Called when a batch is refused. |
The client is memoized on everything except identify and onError. Your backend refreshes a signature far more often than a key changes, and rebuilding the client on each one would throw away the events it was about to send. The identity is pushed into the existing client instead, which flushes the buffer first.
The provider flushes on unmount, so the last event of a visit, very often the interesting one, is not the one that never arrives.
Server rendering
Everything here is client-side. createAnalytics reads window defensively, so a provider rendered on the server produces no events and throws nothing. Recording starts when the tree hydrates.
