The HTML script tag will record pageviews in many React apps because it patches history. It is still the wrong default inside a TypeScript SPA. Use @siteline.tools/react, which wraps @siteline.tools/tracker. You mount one component. You call track() from click handlers. You do not paste a second snippet on every route file.
If your router is Next.js App Router, read the manual pageview step. The core tracker hooks popstate and patches pushState / replaceState. Some App Router navigations bypass those. Empty in-app pageviews are that gap, not a broken site id.
Full SDK surface: React docs. Event limits: custom events.
1. Enable the analytics site
In SiteLine, enable analytics on the domain (Sites list → Enable analytics, or Add analytics). Open Install. Copy the public site id. Add every hostname the app is served from, including preview hosts, to the allowlist. Events from a host that is not listed never ingest.
You still need a SiteLine origin for endpoint. That is https://YOUR_SITELINE_ORIGIN/api/analytics/event, or a same-origin path if you already proxy collect.
No account yet: register.
2. Install both packages
The React package does not bundle the core. Install both.
npm install @siteline.tools/react @siteline.tools/tracker
pnpm add @siteline.tools/react @siteline.tools/tracker
3. Mount <Analytics /> once
Put it near the root. It calls init() on mount and records the initial pageview.
import { Analytics } from '@siteline.tools/react';
export function App() {
return (
<>
<Analytics
siteId="YOUR_SITE_ID"
endpoint="https://YOUR_SITELINE_ORIGIN/api/analytics/event"
/>
{/* your app */}
</>
);
}
Do not mount it inside a page that remounts on every navigation. One init. The tracker ignores a second init().
Options (all optional except siteId):
endpoint— default/api/analytics/event(relative to the page origin, which is wrong unless you proxy). Prefer an absolute SiteLine URL, or a first-party path you actually proxy.trackOutboundLinks— defaultfalse.trackFileDownloads— defaultfalse.fileDownloadExtensions— array of extensions when you override the default list.
Outbound and download flags emit billable custom events. Leave them off unless you need them.
4. Track named product events
import { useAnalytics } from '@siteline.tools/react';
export function SignupButton() {
const { track } = useAnalytics();
return (
<button
type="button"
onClick={() => track('signup_clicked', { plan: 'starter' })}
>
Start trial
</button>
);
}
track requires a name. Properties are optional. At most 10 keys, key length ≤ 64, string values ≤ 256, values string | number | boolean. Extra keys are dropped; long strings are truncated. Client and ingest both enforce this.
Keep names lowercase and stable. Goals match event name exactly. Signup Clicked and signup_clicked are different goals.
5. Send pageviews on App Router changes
If client navigations do not appear as pageviews, add a tiny listener and call trackPageview():
import { useAnalytics } from '@siteline.tools/react';
import { useEffect } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
export function AnalyticsPageviews() {
const { trackPageview } = useAnalytics();
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
trackPageview();
}, [pathname, searchParams, trackPageview]);
return null;
}
Mount that next to <Analytics />, not instead of it. <Analytics /> still owns init().
You do not need this helper on a plain Vite SPA that uses pushState the way the tracker expects. Add it when Live shows the first load and then goes quiet during in-app routing.
6. Confirm ingest
Load the app, click something that fires track, open SiteLine Live (about 30 minutes of visitors, pageviews, top pages). Then check the Events breakdown for your custom names.
If nothing arrives:
siteIdmatches Install.endpointis absolute, HTTPS, and not your Next.js/apifolder unless that folder proxies to SiteLine.- Hostname allowlist includes
localhostonly if you actually want local traffic (usually you do not). - You did not call
track()before<Analytics />mounted.useAnalytics()is a no-op untilinit().
Collect is POST /api/analytics/event (alias POST /js/event). The tracker prefers keepalive fetch with credentials: 'omit'.
Vanilla core
Outside React, import the tracker directly:
import { init, track } from '@siteline.tools/tracker';
init({
siteId: 'YOUR_SITE_ID',
endpoint: 'https://YOUR_SITELINE_ORIGIN/api/analytics/event',
});
track('purchase', { value: 49 });
Do not mix the HTML script tag and the SDK on the same page. Two inits, two pageviews.
FAQ
Do I still need the script tag?
No. <Analytics /> is the install. The script snippet is for HTML sites. Using both double-counts pageviews.
Are React events extra billable volume?
Yes. Pageviews and custom events both count. Goals you define in the dashboard do not emit extra events. Auto outbound/download tracking does.
Why is endpoint optional if omitting it is dangerous?
The default is a relative /api/analytics/event. That posts to your origin. Unless you reverse-proxy that path to SiteLine, those requests 404 in your app. Set an absolute collect URL, or proxy both script and collect as in the proxy guide.