Privacy-focused analytics for React, Next.js, and React Native. Easily integrate Fathom Analytics into your applications with automatic pageview tracking, custom event tracking, and full TypeScript support.
- Quick Start
- Why react-fathom?
- Features
- Installation
- Usage
- API Reference
- Troubleshooting
- Contributing
- License
npm install react-fathom fathom-client// App.tsx or layout.tsx
import { FathomProvider } from 'react-fathom'
function App() {
return (
<FathomProvider siteId="YOUR_FATHOM_SITE_ID">
<YourApp />
</FathomProvider>
)
}// Any component
import { useFathom } from 'react-fathom'
function MyComponent() {
const { trackEvent } = useFathom()
return (
<button onClick={() => trackEvent('button-click')}>
Click me
</button>
)
}That's it! Pageviews are tracked automatically.
Fathom Analytics is a privacy-focused alternative to Google Analytics. Unlike traditional analytics platforms:
- No cookies required - GDPR, CCPA, and PECR compliant out of the box
- No personal data collection - Respects user privacy by design
- No consent banners needed - Simplified compliance for your websites
- Fast and lightweight - Won't slow down your site
- React-native integration - Works seamlessly with React's component model and hooks
- Automatic tracking - Pageviews tracked automatically on route changes
- Next.js optimized - First-class support for both App Router and Pages Router
- React Native support - Full mobile support with offline queuing
- TypeScript-first - Complete type definitions for a great developer experience
- Tree-shakeable - Only bundle what you use
New to Fathom? Get a $10 credit when you sign up using this referral link.
- π Zero-config Fathom Analytics integration for React
- π¦ Tree-shakeable - Only bundle what you use
- π Automatic pageview tracking for Next.js (Pages Router & App Router)
- π± React Native support with offline queuing and navigation tracking
- πͺ Full TypeScript support with type definitions
- π― Flexible - Works with any React app, Next.js, or React Native
- β‘ Lightweight - Minimal bundle size impact
Via npm
npm install react-fathom fathom-clientVia Yarn
yarn add react-fathom fathom-clientreact>= 16.8react-dom>= 16.8 (only if using web)fathom-client>= 3.0.0 (only if using web, not needed for React Native)next>= 10.0.0 (only if using Next.js providers)react-native>= 0.60.0 (only if using React Native)react-native-webview>= 11.0.0 (only if using React Native)
Wrap your app with FathomProvider:
import { FathomProvider } from 'react-fathom'
function App() {
return <FathomProvider siteId="YOUR_SITE_ID">{/* Your app */}</FathomProvider>
}Access Fathom methods via the useFathom hook:
import { useFathom } from 'react-fathom'
function MyComponent() {
const { trackPageview, trackEvent, trackGoal, load } = useFathom()
const handleClick = () => {
trackEvent('button-click', { _value: 100 }) // Optional: value in cents
}
const handlePurchase = () => {
trackGoal('purchase', 2999) // $29.99 in cents
}
return (
<>
<button onClick={handleClick}>Sign Up</button>
<button onClick={handlePurchase}>Buy Now</button>
</>
)
}Track events and pageviews with convenience hooks:
import {
useTrackOnMount,
useTrackOnClick,
useTrackOnVisible,
} from 'react-fathom'
function MyComponent() {
// Track pageview on mount
useTrackOnMount({ url: '/custom-page' })
// Track event on click
const handleClick = useTrackOnClick({
eventName: 'button-click',
_value: 100, // Optional: value in cents
callback: (e) => {
console.log('Tracked click!', e)
},
})
// Track event when element becomes visible
const ref = useTrackOnVisible({
eventName: 'section-viewed',
_value: 1, // Optional: value in cents
callback: (entry) => {
console.log('Element is visible!', entry)
},
})
return (
<>
<button onClick={handleClick}>Sign Up</button>
<div ref={ref}>This will be tracked when visible</div>
</>
)
}Use declarative components for tracking:
import { TrackPageview, TrackClick, TrackVisible } from 'react-fathom'
function MyPage() {
return (
<>
{/* Track pageview on mount */}
<TrackPageview url="/custom-page">
<div>Page content</div>
</TrackPageview>
{/* Track click events */}
<TrackClick eventName="button-click" _value={100}>
<button>Sign Up</button>
</TrackClick>
{/* Track when element becomes visible */}
<TrackVisible eventName="section-viewed" _value={1}>
<div>Hero section</div>
</TrackVisible>
</>
)
}Recommended: Use NextFathomProviderApp for easy integration in App Router layouts:
// app/layout.tsx
import { NextFathomProviderApp } from 'react-fathom/next'
export default function RootLayout({ children }) {
return (
<html>
<body>
<NextFathomProviderApp siteId="YOUR_SITE_ID">
{children}
</NextFathomProviderApp>
</body>
</html>
)
}Alternative: You can also use FathomProvider with NextFathomTrackViewApp separately if you need more control:
// app/layout.tsx
import { FathomProvider } from 'react-fathom'
import { NextFathomTrackViewApp } from 'react-fathom/next'
export default function RootLayout({ children }) {
return (
<html>
<body>
<FathomProvider siteId="YOUR_SITE_ID">
<NextFathomTrackViewApp />
{children}
</FathomProvider>
</body>
</html>
)
}Note: Since
FathomProvideruses React hooks, you'll need to wrap it in a Client Component when using it directly in a Server Component layout.NextFathomProviderApphandles this for you automatically.
Use FathomProvider with NextFathomTrackViewPages for automatic route tracking:
// pages/_app.tsx
import { FathomProvider } from 'react-fathom'
import { NextFathomTrackViewPages } from 'react-fathom/next'
function MyApp({ Component, pageProps }) {
return (
<FathomProvider siteId="YOUR_SITE_ID">
<NextFathomTrackViewPages />
<Component {...pageProps} />
</FathomProvider>
)
}
export default MyAppThe FathomProvider supports setting default options that automatically merge with any options passed to tracking calls. This is useful for setting app-wide defaults like custom event IDs or referrer information.
Default options are spread first, then any options you pass to individual tracking calls are spread second. This means:
- Default options provide base values for all tracking calls
- Provided options override defaults when specified
- You can set defaults once and forget about them
<FathomProvider
siteId="YOUR_SITE_ID"
defaultEventOptions={{ _site_id: 'my-app' }}
>
{/* All trackEvent calls will include _site_id: 'my-app' unless overridden */}
</FathomProvider>// Inside your component
const { trackEvent } = useFathom()
// Uses default: { _site_id: 'my-app' }
trackEvent('button-click')
// Merges with default: { _site_id: 'my-app', _value: 100 }
trackEvent('purchase', { _value: 100 })
// Overrides default: { _site_id: 'custom-site', _value: 50 }
trackEvent('special-event', { _site_id: 'custom-site', _value: 50 })When nesting FathomProvider components, child providers inherit defaults from their parent but can override them:
<FathomProvider
siteId="YOUR_SITE_ID"
defaultEventOptions={{ _site_id: 'global' }}
>
{/* Events here use _site_id: 'global' */}
<FathomProvider defaultEventOptions={{ _site_id: 'dashboard' }}>
{/* Events here use _site_id: 'dashboard' */}
</FathomProvider>
</FathomProvider>The FathomProvider accepts an optional client prop that allows you to provide a custom Fathom client implementation. This is useful for:
- React Native apps that need a custom tracking implementation
- Testing with mock clients
- Server-side rendering scenarios
- Custom analytics pipelines that wrap Fathom
Your custom client must implement the FathomClient interface:
import type { FathomClient, EventOptions, LoadOptions, PageViewOptions } from 'react-fathom'
const myCustomClient: FathomClient = {
load: (siteId: string, options?: LoadOptions) => {
// Initialize your tracking
},
trackPageview: (opts?: PageViewOptions) => {
// Track pageview
},
trackEvent: (eventName: string, opts?: EventOptions) => {
// Track custom event
},
trackGoal: (code: string, cents: number) => {
// Track goal conversion
},
setSite: (id: string) => {
// Change site ID
},
blockTrackingForMe: () => {
// Block tracking
},
enableTrackingForMe: () => {
// Enable tracking
},
isTrackingEnabled: () => {
// Return tracking status
return true
},
}For React Native apps, use the dedicated /native export. This uses a hidden WebView to load Fathom's official tracking script, ensuring full compatibility with Fathom Analytics.
Install the required peer dependency:
npm install react-native-webview
# or
yarn add react-native-webviewBasic setup:
import { NativeFathomProvider } from 'react-fathom/native'
function App() {
return (
<NativeFathomProvider
siteId="YOUR_SITE_ID"
debug={__DEV__}
trackAppState
onReady={() => console.log('Fathom ready!')}
>
<YourApp />
</NativeFathomProvider>
)
}Note: The provider renders a hidden WebView (0x0 pixels) that loads the Fathom script. Events are queued until the WebView is ready, then automatically sent.
Track screen navigation as pageviews with React Navigation:
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'
import { NativeFathomProvider, useNavigationTracking } from 'react-fathom/native'
function App() {
const navigationRef = useNavigationContainerRef()
return (
<NativeFathomProvider siteId="YOUR_SITE_ID">
<NavigationContainer ref={navigationRef}>
<NavigationTracker navigationRef={navigationRef} />
<RootNavigator />
</NavigationContainer>
</NativeFathomProvider>
)
}
function NavigationTracker({ navigationRef }) {
useNavigationTracking({
navigationRef,
transformRouteName: (name) => `/screens/${name}`,
})
return null
}Track when users foreground/background your app:
import { useAppStateTracking } from 'react-fathom/native'
function AppTracker() {
useAppStateTracking({
foregroundEventName: 'app-resumed',
backgroundEventName: 'app-paused',
onStateChange: (state) => console.log('App state:', state),
})
return null
}If you use Fathom's custom domains feature, specify your domain:
<NativeFathomProvider
siteId="YOUR_SITE_ID"
scriptDomain="your-custom-domain.com"
>
<YourApp />
</NativeFathomProvider>For advanced use cases, you can manually set up the WebView client:
import { useRef, useMemo, useCallback } from 'react'
import {
FathomWebView,
createWebViewClient,
FathomProvider,
type FathomWebViewRef,
} from 'react-fathom/native'
function App() {
const webViewRef = useRef<FathomWebViewRef>(null)
const client = useMemo(
() => createWebViewClient(() => webViewRef.current, { debug: __DEV__ }),
[]
)
const handleReady = useCallback(() => {
client.setWebViewReady()
}, [client])
return (
<FathomProvider client={client} siteId="YOUR_SITE_ID">
<FathomWebView
ref={webViewRef}
siteId="YOUR_SITE_ID"
onReady={handleReady}
/>
<YourApp />
</FathomProvider>
)
}import { FathomProvider, type FathomClient } from 'react-fathom'
const mockClient: FathomClient = {
load: jest.fn(),
trackPageview: jest.fn(),
trackEvent: jest.fn(),
trackGoal: jest.fn(),
setSite: jest.fn(),
blockTrackingForMe: jest.fn(),
enableTrackingForMe: jest.fn(),
isTrackingEnabled: jest.fn(() => true),
}
// In your tests
render(
<FathomProvider client={mockClient}>
<ComponentUnderTest />
</FathomProvider>
)
// Assert tracking calls
expect(mockClient.trackEvent).toHaveBeenCalledWith('button-click', { _value: 100 })Main provider component for React apps. Supports composable nesting - nested providers can override client, defaultPageviewOptions, or defaultEventOptions.
Props:
siteId(string, optional): Your Fathom Analytics site IDclient(FathomClient, optional): Custom Fathom client instanceclientRef(MutableRefObject<FathomClient | null>, optional): Ref that will be populated with the resolved client instance, allowing the parent component to access the client directlyclientOptions(LoadOptions, optional): Options passed tofathom-clientdefaultPageviewOptions(PageViewOptions, optional): Default options merged into alltrackPageviewcallsdefaultEventOptions(EventOptions, optional): Default options merged into alltrackEventcalls
Example:
<FathomProvider
siteId="YOUR_SITE_ID"
defaultPageviewOptions={{ referrer: 'https://example.com' }}
defaultEventOptions={{ _site_id: 'global-site' }}
>
{/* Your app */}
</FathomProvider>Using clientRef for parent access:
import { useRef } from 'react'
import { FathomProvider, FathomClient } from 'react-fathom'
function App() {
const clientRef = useRef<FathomClient>(null)
const handleDeepLink = (url: string) => {
// Parent can track events directly via the ref
clientRef.current?.trackEvent('deep_link', { _url: url })
}
return (
<FathomProvider siteId="YOUR_SITE_ID" clientRef={clientRef}>
<YourApp onDeepLink={handleDeepLink} />
</FathomProvider>
)
}Client component wrapper that combines FathomProvider and NextFathomTrackViewApp for easy integration in Next.js App Router layouts. This component is marked with 'use client' and can be used directly in Server Components like the root layout.tsx file.
Props:
siteId(string, optional): Your Fathom Analytics site IDclient(FathomClient, optional): Custom Fathom client instanceclientOptions(LoadOptions, optional): Options passed tofathom-clientdefaultPageviewOptions(PageViewOptions, optional): Default options merged into alltrackPageviewcallsdefaultEventOptions(EventOptions, optional): Default options merged into alltrackEventcallsdisableAutoTrack(boolean, optional): Disable automatic pageview tracking on route changes (defaults to false)children(ReactNode, required): Child components to render
Example:
// app/layout.tsx
import { NextFathomProviderApp } from 'react-fathom/next'
export default function RootLayout({ children }) {
return (
<html>
<body>
<NextFathomProviderApp siteId="YOUR_SITE_ID">
{children}
</NextFathomProviderApp>
</body>
</html>
)
}Component that tracks pageviews for Next.js App Router. Must be used within a FathomProvider.
Props:
disableAutoTrack(boolean, optional): Disable automatic pageview tracking on route changes (defaults to false)
Example:
<FathomProvider siteId="YOUR_SITE_ID">
<NextFathomTrackViewApp />
{/* Your app */}
</FathomProvider>Component that tracks pageviews for Next.js Pages Router. Must be used within a FathomProvider.
Props:
disableAutoTrack(boolean, optional): Disable automatic pageview tracking on route changes (defaults to false)
Example:
<FathomProvider siteId="YOUR_SITE_ID">
<NextFathomTrackViewPages />
{/* Your app */}
</FathomProvider>Hook to access Fathom methods and context.
Returns:
trackPageview(options?): Track a pageview (automatically mergesdefaultPageviewOptions)trackEvent(eventName, options?): Track a custom event (automatically mergesdefaultEventOptions)trackGoal(code, cents): Track a goal conversionload(siteId, options?): Load Fathom with a site IDsetSite(siteId): Change the site IDblockTrackingForMe(): Block tracking for current userenableTrackingForMe(): Enable tracking for current userisTrackingEnabled(): Check if tracking is enabledclient: The Fathom client instancedefaultPageviewOptions: Current default pageview optionsdefaultEventOptions: Current default event options
Hook to track a pageview when a component mounts.
Options:
url(string, optional): URL to trackreferrer(string, optional): Referrer URL- All other
PageViewOptionsfromfathom-client
Hook that returns a click handler function to track events.
Options:
eventName(string, required): Event name to trackpreventDefault(boolean, optional): Whether to prevent default behavior (defaults to false)callback((e?: MouseEvent) => void, optional): Callback function to run after tracking- All other
EventOptionsfromfathom-client
Hook that returns a ref to attach to an element. Tracks an event when the element becomes visible.
Options:
eventName(string, required): Event name to trackcallback((entry: IntersectionObserverEntry) => void, optional): Callback function to run after trackingthreshold(number, optional): IntersectionObserver threshold (defaults to 0.1)rootMargin(string, optional): IntersectionObserver rootMargin- All other
EventOptionsfromfathom-client
Component that tracks a pageview when it mounts.
Props:
url(string, optional): URL to trackreferrer(string, optional): Referrer URLchildren(ReactNode, optional): Child elements to render- All other
PageViewOptionsfromfathom-client
Component that tracks an event when clicked.
Props:
eventName(string, required): Event name to trackpreventDefault(boolean, optional): Whether to prevent default behavior (defaults to false)children(ReactNode, required): Child element(s) to render- All other
EventOptionsfromfathom-client
Component that tracks an event when it becomes visible.
Props:
eventName(string, required): Event name to trackthreshold(number, optional): IntersectionObserver threshold (defaults to 0.1)rootMargin(string, optional): IntersectionObserver rootMarginchildren(ReactNode, required): Child element(s) to renderas(string, optional): HTML element type to render (defaults to 'div')- All other
EventOptionsfromfathom-client
The /native export provides React Native-specific components and hooks. It uses a hidden WebView to load Fathom's official tracking script, ensuring full compatibility with Fathom Analytics (both Fathom Pro and self-hosted Fathom Lite).
Convenience provider for React Native apps that manages a hidden WebView with Fathom's tracking script.
Props:
siteId(string, required): Your Fathom Analytics site IDloadOptions(LoadOptions, optional): Options passed tofathom.load()in the WebViewscriptDomain(string, optional): Custom domain for Fathom script (defaults to 'cdn.usefathom.com')defaultPageviewOptions(PageViewOptions, optional): Default options merged into alltrackPageviewcallsdefaultEventOptions(EventOptions, optional): Default options merged into alltrackEventcallstrackAppState(boolean, optional): Enable automatic app state tracking (defaults to false)debug(boolean, optional): Enable debug logging (defaults to false)onReady(() => void, optional): Called when the Fathom script has loadedonError((error: string) => void, optional): Called when an error occurs loading the scriptclientRef(MutableRefObject<WebViewFathomClient | null>, optional): Ref that will be populated with the WebView-based client instance, allowing the parent component to access the client directly (includes queue management methods)children(ReactNode, required): Child components to render
Example:
<NativeFathomProvider
siteId="YOUR_SITE_ID"
debug={__DEV__}
trackAppState
onReady={() => console.log('Analytics ready!')}
onError={(err) => console.error('Analytics error:', err)}
>
<App />
</NativeFathomProvider>Using clientRef for parent access:
import { useRef } from 'react'
import { NativeFathomProvider, WebViewFathomClient } from 'react-fathom/native'
function App() {
const clientRef = useRef<WebViewFathomClient>(null)
const handleDeepLink = (url: string) => {
// Parent can track events directly via the ref
clientRef.current?.trackEvent('deep_link', { _url: url })
// Can also check queue status (React Native specific)
console.log('Queued events:', clientRef.current?.getQueueLength())
}
return (
<NativeFathomProvider siteId="YOUR_SITE_ID" clientRef={clientRef}>
<YourApp onDeepLink={handleDeepLink} />
</NativeFathomProvider>
)
}Hidden WebView component that loads and manages the Fathom Analytics script. Used internally by NativeFathomProvider, but can be used directly for advanced setups.
Props:
siteId(string, required): Your Fathom Analytics site IDloadOptions(LoadOptions, optional): Options passed tofathom.load()scriptDomain(string, optional): Custom domain for Fathom script (defaults to 'cdn.usefathom.com')onReady(() => void, optional): Called when the Fathom script has loadedonError((error: string) => void, optional): Called when an error occursdebug(boolean, optional): Enable debug logging (defaults to false)
Ref Methods (FathomWebViewRef):
trackPageview(opts?): Track a pageviewtrackEvent(eventName, opts?): Track a custom eventtrackGoal(code, cents): Track a goal conversionblockTrackingForMe(): Block tracking for current userenableTrackingForMe(): Enable tracking for current userisReady(): Check if the WebView is ready
Factory function to create a client that communicates with a FathomWebView.
Parameters:
getWebViewRef(() => FathomWebViewRef | null): Function that returns the WebView refoptions(WebViewClientOptions, optional):debug(boolean): Enable debug logging (defaults to false)enableQueue(boolean): Enable command queuing before WebView is ready (defaults to true)maxQueueSize(number): Maximum commands to queue (defaults to 100)
Returns: A FathomClient instance with additional methods:
processQueue(): Manually process queued commands (returns number of processed)getQueueLength(): Get the current queue lengthsetWebViewReady(): Call when WebView signals it's ready (flushes queue)
Hook that tracks app state changes (foreground/background) as Fathom events.
Options:
foregroundEventName(string, optional): Event name for foreground (defaults to 'app-foreground')backgroundEventName(string, optional): Event name for background (defaults to 'app-background')eventOptions(EventOptions, optional): Additional options for app state eventsonStateChange((state: 'active' | 'background' | 'inactive') => void, optional): Callback on state change
Hook that tracks React Navigation screen changes as pageviews.
Options:
navigationRef(RefObject, required): React Navigation container reftransformRouteName((name: string) => string, optional): Transform route names before trackingshouldTrackRoute((name: string, params?: object) => boolean, optional): Filter which routes to trackincludeParams(boolean, optional): Include route params in tracked URL (defaults to false)
Example:
const navigationRef = useNavigationContainerRef()
useNavigationTracking({
navigationRef,
transformRouteName: (name) => `/app/${name.toLowerCase()}`,
shouldTrackRoute: (name) => !name.startsWith('Modal'),
includeParams: true,
})This library is optimized for tree-shaking. When you import only what you need:
import { useFathom } from 'react-fathom'Bundlers will automatically exclude unused code, keeping your bundle size minimal.
Full TypeScript support is included. Types are automatically generated and exported.
For convenience, react-fathom re-exports the core types from fathom-client so you don't need to import from multiple packages:
import type {
// From react-fathom
FathomClient,
FathomContextInterface,
FathomProviderProps,
// Re-exported from fathom-client
EventOptions,
LoadOptions,
PageViewOptions,
} from 'react-fathom'
// No need for this anymore:
// import type { EventOptions } from 'fathom-client'This simplifies your imports when building custom clients or working with typed event options.
1. Verify your site ID
Your site ID should match exactly what's shown in your Fathom dashboard. It's typically an 8-character alphanumeric string like ABCD1234.
// Double-check this value
<FathomProvider siteId="ABCD1234">2. Check for ad blockers
Many ad blockers and privacy extensions block analytics scripts. To test:
- Open an incognito/private window with extensions disabled
- Or temporarily whitelist your development domain
3. Domain restrictions
Fathom only tracks events from domains you've configured. For local development:
<FathomProvider
siteId="YOUR_SITE_ID"
clientOptions={{
includedDomains: ['localhost', 'yourdomain.com']
}}
>4. Inspect network requests
Open your browser's Network tab and look for requests to cdn.usefathom.com. If you see:
- No requests: The script isn't loading (check provider setup)
- Blocked requests: Ad blocker is interfering
- Failed requests: Check your site ID and domain configuration
If you're seeing double pageviews, you likely have multiple tracking setups:
// WRONG: Both auto tracking AND manual tracking
<FathomProvider siteId="YOUR_SITE_ID">
<NextFathomTrackViewApp /> {/* This tracks pageviews */}
{/* AND clientOptions.auto defaults to true, which also tracks */}
</FathomProvider>
// CORRECT: Use one or the other
<FathomProvider siteId="YOUR_SITE_ID" clientOptions={{ auto: false }}>
<NextFathomTrackViewApp />
</FathomProvider>This was the old behavior. As of the latest version, useFathom() returns stub methods that warn in development when called outside a provider. If you're seeing undefined:
- Update to the latest version:
npm update react-fathom - Ensure your component is inside a
FathomProvider
Server Components can't use hooks directly. Use the pre-configured client component:
// app/layout.tsx
import { NextFathomProviderApp } from 'react-fathom/next'
export default function RootLayout({ children }) {
return (
<html>
<body>
<NextFathomProviderApp siteId="YOUR_SITE_ID">
{children}
</NextFathomProviderApp>
</body>
</html>
)
}If you need a custom setup, create your own client component wrapper:
// components/AnalyticsProvider.tsx
'use client'
import { FathomProvider } from 'react-fathom'
export function AnalyticsProvider({ children }) {
return (
<FathomProvider siteId={process.env.NEXT_PUBLIC_FATHOM_SITE_ID}>
{children}
</FathomProvider>
)
}Ensure your environment variable is prefixed with NEXT_PUBLIC_ to be available client-side:
# .env.local
NEXT_PUBLIC_FATHOM_SITE_ID=YOUR_SITE_ID # β Correct
FATHOM_SITE_ID=YOUR_SITE_ID # β Won't work client-side1. Verify react-native-webview is installed
The native module requires react-native-webview:
npm install react-native-webview
# For iOS, also run:
cd ios && pod install2. Check WebView is ready
Events are queued until the WebView loads. Use the onReady callback to verify:
<NativeFathomProvider
siteId="YOUR_SITE_ID"
debug={true}
onReady={() => console.log('Fathom WebView ready!')}
onError={(err) => console.error('Fathom error:', err)}
>3. Verify network connectivity
The WebView needs network access to load the Fathom script. Events are queued before the WebView is ready but won't send if the script fails to load.
4. Check for WebView restrictions
Some enterprise MDM solutions or app configurations may block WebViews from loading external scripts. Verify that cdn.usefathom.com (or your custom domain) is accessible.
5. Debug with logging
Enable debug mode to see all tracking activity:
<NativeFathomProvider
siteId="YOUR_SITE_ID"
debug={__DEV__} // Logs all tracking calls
>For web, check the browser console. For React Native, enable debug mode:
// React Native
<NativeFathomProvider
siteId="YOUR_SITE_ID"
debug={__DEV__}
>Fathom's dashboard updates in real-time. Open your dashboard alongside your app to see events as they're tracked.
For debugging, replace the real client with a mock that logs everything:
const debugClient = {
load: (id, opts) => console.log('load:', id, opts),
trackPageview: (opts) => console.log('pageview:', opts),
trackEvent: (name, opts) => console.log('event:', name, opts),
trackGoal: (code, cents) => console.log('goal:', code, cents),
setSite: (id) => console.log('setSite:', id),
blockTrackingForMe: () => console.log('blocked'),
enableTrackingForMe: () => console.log('enabled'),
isTrackingEnabled: () => true,
}
<FathomProvider client={debugClient}>- Open an issue on GitHub
- Search existing issues for solutions
- Fathom Analytics documentation for platform-specific questions
Contributions are welcome! Whether it's bug fixes, new features, documentation improvements, or examples, we appreciate your help.
| Type | Description |
|---|---|
| Bug Reports | Found a bug? Open an issue with reproduction steps |
| Feature Requests | Have an idea? Discuss it in an issue first |
| Bug Fixes | PRs for documented issues are always welcome |
| Documentation | Help improve docs, add examples, fix typos |
| Tests | Increase test coverage or add edge case tests |
Prerequisites:
- Node.js 18+
- npm 9+
1. Clone and install:
git clone https://github.com/ryanhefner/react-fathom.git
cd react-fathom
npm install2. Run the development workflow:
# Run tests in watch mode during development
npm run test:watch
# Run the full test suite
npm test
# Build the package
npm run build
# Type check without emitting
npm run typecheckreact-fathom/
βββ src/
β βββ index.ts # Main entry point
β βββ FathomProvider.tsx # Core provider component
β βββ FathomContext.tsx # React context
β βββ types.ts # TypeScript definitions
β βββ hooks/ # React hooks
β β βββ useFathom.ts
β β βββ useTrackOnClick.ts
β β βββ useTrackOnMount.ts
β β βββ useTrackOnVisible.ts
β βββ components/ # Declarative tracking components
β β βββ TrackClick.tsx
β β βββ TrackPageview.tsx
β β βββ TrackVisible.tsx
β βββ next/ # Next.js-specific exports
β β βββ index.ts
β βββ native/ # React Native exports
β βββ index.ts
β βββ FathomWebView.tsx
β βββ createWebViewClient.ts
β βββ NativeFathomProvider.tsx
β βββ useNavigationTracking.ts
β βββ useAppStateTracking.ts
βββ examples/ # Example applications
β βββ next-app/ # Next.js App Router example
β βββ next-pages/ # Next.js Pages Router example
βββ dist/ # Built output (generated)
We use Vitest for testing. All new features should include tests.
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverageWriting tests:
// src/hooks/useMyHook.test.tsx
import { renderHook } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { useMyHook } from './useMyHook'
import { FathomProvider } from '../FathomProvider'
describe('useMyHook', () => {
it('should track events correctly', () => {
const mockClient = {
trackEvent: vi.fn(),
// ... other required methods
}
const wrapper = ({ children }) => (
<FathomProvider client={mockClient}>{children}</FathomProvider>
)
const { result } = renderHook(() => useMyHook(), { wrapper })
result.current.doSomething()
expect(mockClient.trackEvent).toHaveBeenCalledWith('expected-event', {})
})
})- TypeScript: All code should be fully typed
- Formatting: We use Prettier (run
npm run formatbefore committing) - Linting: ESLint catches common issues (run
npm run lint) - Naming:
- Components: PascalCase (
TrackClick.tsx) - Hooks: camelCase with
useprefix (useFathom.ts) - Types: PascalCase (
FathomClient)
- Components: PascalCase (
- Fork the repository
- Create a branch from
main:git checkout -b fix/my-bug-fix # or git checkout -b feature/my-new-feature - Make your changes with clear, focused commits
- Add or update tests for your changes
- Ensure CI passes:
npm run lint npm test npm run build - Submit a PR with a clear description of what and why
Use clear, descriptive commit messages:
feat: add useTrackOnScroll hook for scroll tracking
fix: resolve duplicate pageview tracking in Next.js
docs: add troubleshooting section for ad blockers
test: add tests for native offline queue
refactor: simplify FathomContext default values
Prefixes: feat, fix, docs, test, refactor, chore, perf
MIT Β© Ryan Hefner