Frameworks

Expo and React Native Web
Expo analytics with Stickybeak works on Expo web builds, not native iOS or Android apps; this guide shows where to add the script and how to track page views, goals and purchase revenue. It is a lighter alternative to Google Analytics for Expo web, with live visitors, goals and revenue attribution in one script.
Add the script
Expo Router with static or server output
For web.output: "static" or "server", create src/app/+html.tsx (or app/+html.tsx for root routes):
import { ScrollViewStyleReset } from "expo-router/html";
import type { PropsWithChildren } from "react";
export default function Root({ children }: PropsWithChildren) {
const siteId = process.env.EXPO_PUBLIC_STICKYBEAK_SITE_ID;
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<ScrollViewStyleReset />
{siteId && (
<script
defer
data-site={siteId}
data-domain="example.com"
src="https://app.usestickybeak.com/_cfw/s.js"
/>
)}
</head>
<body>{children}</body>
</html>
);
}
Set EXPO_PUBLIC_STICKYBEAK_SITE_ID when you run npx expo export --platform web for production, and leave it unset in development.
Single-page output
With web.output: "single", all routes share one HTML template. Create it with:
npx expo customize public/index.html
Then paste the standard snippet before </head> in public/index.html:
Paste the same script tag before </head> in public/index.html.
Check it's working
Test the deployed site, then verify your install.
Track goals
React Native components cannot use data-goal; call the web-only typed helper below. See Custom goals.
import { Platform } from "react-native";
type GoalProps = Record<string, string | number | boolean>;
declare global {
interface Window {
stickybeak?: (goal: string, props?: GoalProps) => void;
}
}
export function track(goal: string, props?: GoalProps): void {
if (Platform.OS !== "web" || typeof window === "undefined") return;
window.stickybeak?.(goal, props);
}
import { Pressable, Text } from "react-native";
import { track } from "@/lib/stickybeak";
export function UpgradeButton() {
return (
<Pressable onPress={() => track("upgrade_click", { plan: "pro" })}>
<Text>Upgrade to Pro</Text>
</Pressable>
);
}
Track purchases
After confirming an order in the browser, send:
window.stickybeak?.("purchase", { revenue: 49, currency: "USD", order_id: "ORDER_ID" });
Use real values; see Revenue.
Page changes
Expo web counts new paths, including back and forward; router.replace() does not. See Single-page apps.
If it isn't working
Check the production site ID and domain, then see Troubleshooting.