{"id":35432694,"url":"https://github.com/ryanhefner/react-fathom","last_synced_at":"2026-01-13T19:57:30.147Z","repository":{"id":329372013,"uuid":"728904505","full_name":"ryanhefner/react-fathom","owner":"ryanhefner","description":"😻 Privacy-focused Fathom Analytics for React, Next.js, and React Native with automatic pageview tracking and TypeScript support","archived":false,"fork":false,"pushed_at":"2026-01-07T22:26:25.000Z","size":469,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-09T05:26:58.842Z","etag":null,"topics":["analytics","fathom","fathom-analytics","mobile","next","nextjs","pageview","privacy","privacy-first","react","react-hooks","react-native","tracking","treeshakable","typescript","typescript-react","typescript-react-components"],"latest_commit_sha":null,"homepage":"https://github.com/ryanhefner/react-fathom#readme","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ryanhefner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"ryanhefner","patreon":"ryanhefner","open_collective":"ryanhefner","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2023-12-08T00:29:54.000Z","updated_at":"2026-01-07T22:33:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ryanhefner/react-fathom","commit_stats":null,"previous_names":["ryanhefner/react-fathom"],"tags_count":13,"template":false,"template_full_name":"ryanhefner/package-template-rollup-react","purl":"pkg:github/ryanhefner/react-fathom","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanhefner%2Freact-fathom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanhefner%2Freact-fathom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanhefner%2Freact-fathom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanhefner%2Freact-fathom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryanhefner","download_url":"https://codeload.github.com/ryanhefner/react-fathom/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanhefner%2Freact-fathom/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28398489,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analytics","fathom","fathom-analytics","mobile","next","nextjs","pageview","privacy","privacy-first","react","react-hooks","react-native","tracking","treeshakable","typescript","typescript-react","typescript-react-components"],"created_at":"2026-01-02T21:17:51.094Z","updated_at":"2026-01-13T19:57:30.142Z","avatar_url":"https://github.com/ryanhefner.png","language":"TypeScript","funding_links":["https://github.com/sponsors/ryanhefner","https://patreon.com/ryanhefner","https://opencollective.com/ryanhefner"],"categories":[],"sub_categories":[],"readme":"# react-fathom\n\n[![npm](https://img.shields.io/npm/v/react-fathom?style=flat-square)](https://www.pkgstats.com/pkg:react-fathom)\n[![NPM](https://img.shields.io/npm/l/react-fathom?style=flat-square)](LICENSE)\n[![npm](https://img.shields.io/npm/dt/react-fathom?style=flat-square)](https://www.pkgstats.com/pkg:react-fathom)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/react-fathom?style=flat-square)](https://bundlephobia.com/package/react-fathom)\n[![GitHub stars](https://img.shields.io/github/stars/ryanhefner/react-fathom?style=flat-square)](https://github.com/ryanhefner/react-fathom/stargazers)\n[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square\u0026logo=typescript)](https://www.typescriptlang.org/)\n[![codecov](https://codecov.io/gh/ryanhefner/react-fathom/branch/main/graph/badge.svg)](https://codecov.io/gh/ryanhefner/react-fathom)\n\n**Privacy-focused analytics for React, Next.js, and React Native.** Easily integrate [Fathom Analytics](https://usefathom.com/ref/EKONBS) into your applications with automatic pageview tracking, custom event tracking, and full TypeScript support.\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Why react-fathom?](#why-react-fathom)\n- [Features](#features)\n- [Installation](#install)\n- [Usage](#usage)\n  - [Basic React Setup](#basic-react-setup)\n  - [Next.js App Router](#nextjs-app-router)\n  - [Next.js Pages Router](#nextjs-pages-router)\n  - [React Native](#react-native)\n- [API Reference](#api)\n- [Troubleshooting](#troubleshooting)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Quick Start\n\n```bash\nnpm install react-fathom fathom-client\n```\n\n```tsx\n// App.tsx or layout.tsx\nimport { FathomProvider } from 'react-fathom'\n\nfunction App() {\n  return (\n    \u003cFathomProvider siteId=\"YOUR_FATHOM_SITE_ID\"\u003e\n      \u003cYourApp /\u003e\n    \u003c/FathomProvider\u003e\n  )\n}\n```\n\n```tsx\n// Any component\nimport { useFathom } from 'react-fathom'\n\nfunction MyComponent() {\n  const { trackEvent } = useFathom()\n\n  return (\n    \u003cbutton onClick={() =\u003e trackEvent('button-click')}\u003e\n      Click me\n    \u003c/button\u003e\n  )\n}\n```\n\nThat's it! Pageviews are tracked automatically.\n\n## Why react-fathom?\n\n### Privacy-First Analytics\n\n[Fathom Analytics](https://usefathom.com/ref/EKONBS) is a privacy-focused alternative to Google Analytics. Unlike traditional analytics platforms:\n\n- **No cookies required** - GDPR, CCPA, and PECR compliant out of the box\n- **No personal data collection** - Respects user privacy by design\n- **No consent banners needed** - Simplified compliance for your websites\n- **Fast and lightweight** - Won't slow down your site\n\n### Why Use This Package?\n\n- **React-native integration** - Works seamlessly with React's component model and hooks\n- **Automatic tracking** - Pageviews tracked automatically on route changes\n- **Next.js optimized** - First-class support for both App Router and Pages Router\n- **React Native support** - Full mobile support with offline queuing\n- **TypeScript-first** - Complete type definitions for a great developer experience\n- **Tree-shakeable** - Only bundle what you use\n\n**New to Fathom?** Get a **$10 credit** when you sign up using [this referral link](https://usefathom.com/ref/EKONBS).\n\n## Features\n\n- 🚀 **Zero-config** Fathom Analytics integration for React\n- 📦 **Tree-shakeable** - Only bundle what you use\n- 🔄 **Automatic pageview tracking** for Next.js (Pages Router \u0026 App Router)\n- 📱 **React Native support** with offline queuing and navigation tracking\n- 💪 **Full TypeScript** support with type definitions\n- 🎯 **Flexible** - Works with any React app, Next.js, or React Native\n- ⚡ **Lightweight** - Minimal bundle size impact\n\n## Install\n\nVia [npm](https://npmjs.com/package/react-fathom)\n\n```sh\nnpm install react-fathom fathom-client\n```\n\nVia [Yarn](https://yarn.pm/react-fathom)\n\n```sh\nyarn add react-fathom fathom-client\n```\n\n## Peer Dependencies\n\n- `react` \u003e= 16.8\n- `react-dom` \u003e= 16.8 (only if using web)\n- `fathom-client` \u003e= 3.0.0 (only if using web, not needed for React Native)\n- `next` \u003e= 10.0.0 (only if using Next.js providers)\n- `react-native` \u003e= 0.60.0 (only if using React Native)\n- `react-native-webview` \u003e= 11.0.0 (only if using React Native)\n\n## Usage\n\n### Basic React Setup\n\nWrap your app with `FathomProvider`:\n\n```tsx\nimport { FathomProvider } from 'react-fathom'\n\nfunction App() {\n  return \u003cFathomProvider siteId=\"YOUR_SITE_ID\"\u003e{/* Your app */}\u003c/FathomProvider\u003e\n}\n```\n\n### Using the Hook\n\nAccess Fathom methods via the `useFathom` hook:\n\n```tsx\nimport { useFathom } from 'react-fathom'\n\nfunction MyComponent() {\n  const { trackPageview, trackEvent, trackGoal, load } = useFathom()\n\n  const handleClick = () =\u003e {\n    trackEvent('button-click', { _value: 100 }) // Optional: value in cents\n  }\n\n  const handlePurchase = () =\u003e {\n    trackGoal('purchase', 2999) // $29.99 in cents\n  }\n\n  return (\n    \u003c\u003e\n      \u003cbutton onClick={handleClick}\u003eSign Up\u003c/button\u003e\n      \u003cbutton onClick={handlePurchase}\u003eBuy Now\u003c/button\u003e\n    \u003c/\u003e\n  )\n}\n```\n\n### Convenience Hooks\n\nTrack events and pageviews with convenience hooks:\n\n```tsx\nimport {\n  useTrackOnMount,\n  useTrackOnClick,\n  useTrackOnVisible,\n} from 'react-fathom'\n\nfunction MyComponent() {\n  // Track pageview on mount\n  useTrackOnMount({ url: '/custom-page' })\n\n  // Track event on click\n  const handleClick = useTrackOnClick({\n    eventName: 'button-click',\n    _value: 100, // Optional: value in cents\n    callback: (e) =\u003e {\n      console.log('Tracked click!', e)\n    },\n  })\n\n  // Track event when element becomes visible\n  const ref = useTrackOnVisible({\n    eventName: 'section-viewed',\n    _value: 1, // Optional: value in cents\n    callback: (entry) =\u003e {\n      console.log('Element is visible!', entry)\n    },\n  })\n\n  return (\n    \u003c\u003e\n      \u003cbutton onClick={handleClick}\u003eSign Up\u003c/button\u003e\n      \u003cdiv ref={ref}\u003eThis will be tracked when visible\u003c/div\u003e\n    \u003c/\u003e\n  )\n}\n```\n\n### Declarative Components\n\nUse declarative components for tracking:\n\n```tsx\nimport { TrackPageview, TrackClick, TrackVisible } from 'react-fathom'\n\nfunction MyPage() {\n  return (\n    \u003c\u003e\n      {/* Track pageview on mount */}\n      \u003cTrackPageview url=\"/custom-page\"\u003e\n        \u003cdiv\u003ePage content\u003c/div\u003e\n      \u003c/TrackPageview\u003e\n\n      {/* Track click events */}\n      \u003cTrackClick eventName=\"button-click\" _value={100}\u003e\n        \u003cbutton\u003eSign Up\u003c/button\u003e\n      \u003c/TrackClick\u003e\n\n      {/* Track when element becomes visible */}\n      \u003cTrackVisible eventName=\"section-viewed\" _value={1}\u003e\n        \u003cdiv\u003eHero section\u003c/div\u003e\n      \u003c/TrackVisible\u003e\n    \u003c/\u003e\n  )\n}\n```\n\n### Next.js App Router\n\n**Recommended:** Use `NextFathomProviderApp` for easy integration in App Router layouts:\n\n```tsx\n// app/layout.tsx\nimport { NextFathomProviderApp } from 'react-fathom/next'\n\nexport default function RootLayout({ children }) {\n  return (\n    \u003chtml\u003e\n      \u003cbody\u003e\n        \u003cNextFathomProviderApp siteId=\"YOUR_SITE_ID\"\u003e\n          {children}\n        \u003c/NextFathomProviderApp\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n**Alternative:** You can also use `FathomProvider` with `NextFathomTrackViewApp` separately if you need more control:\n\n```tsx\n// app/layout.tsx\nimport { FathomProvider } from 'react-fathom'\nimport { NextFathomTrackViewApp } from 'react-fathom/next'\n\nexport default function RootLayout({ children }) {\n  return (\n    \u003chtml\u003e\n      \u003cbody\u003e\n        \u003cFathomProvider siteId=\"YOUR_SITE_ID\"\u003e\n          \u003cNextFathomTrackViewApp /\u003e\n          {children}\n        \u003c/FathomProvider\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n\u003e **Note:** Since `FathomProvider` uses React hooks, you'll need to wrap it in a Client Component when using it directly in a Server Component layout. `NextFathomProviderApp` handles this for you automatically.\n\n### Next.js Pages Router\n\nUse `FathomProvider` with `NextFathomTrackViewPages` for automatic route tracking:\n\n```tsx\n// pages/_app.tsx\nimport { FathomProvider } from 'react-fathom'\nimport { NextFathomTrackViewPages } from 'react-fathom/next'\n\nfunction MyApp({ Component, pageProps }) {\n  return (\n    \u003cFathomProvider siteId=\"YOUR_SITE_ID\"\u003e\n      \u003cNextFathomTrackViewPages /\u003e\n      \u003cComponent {...pageProps} /\u003e\n    \u003c/FathomProvider\u003e\n  )\n}\n\nexport default MyApp\n```\n\n## Default Options Merging\n\nThe `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.\n\n### How Merging Works\n\nDefault options are spread first, then any options you pass to individual tracking calls are spread second. This means:\n\n- **Default options** provide base values for all tracking calls\n- **Provided options** override defaults when specified\n- You can set defaults once and forget about them\n\n```tsx\n\u003cFathomProvider\n  siteId=\"YOUR_SITE_ID\"\n  defaultEventOptions={{ _site_id: 'my-app' }}\n\u003e\n  {/* All trackEvent calls will include _site_id: 'my-app' unless overridden */}\n\u003c/FathomProvider\u003e\n```\n\n```tsx\n// Inside your component\nconst { trackEvent } = useFathom()\n\n// Uses default: { _site_id: 'my-app' }\ntrackEvent('button-click')\n\n// Merges with default: { _site_id: 'my-app', _value: 100 }\ntrackEvent('purchase', { _value: 100 })\n\n// Overrides default: { _site_id: 'custom-site', _value: 50 }\ntrackEvent('special-event', { _site_id: 'custom-site', _value: 50 })\n```\n\n### Nested Providers\n\nWhen nesting `FathomProvider` components, child providers inherit defaults from their parent but can override them:\n\n```tsx\n\u003cFathomProvider\n  siteId=\"YOUR_SITE_ID\"\n  defaultEventOptions={{ _site_id: 'global' }}\n\u003e\n  {/* Events here use _site_id: 'global' */}\n\n  \u003cFathomProvider defaultEventOptions={{ _site_id: 'dashboard' }}\u003e\n    {/* Events here use _site_id: 'dashboard' */}\n  \u003c/FathomProvider\u003e\n\u003c/FathomProvider\u003e\n```\n\n## Custom Client Implementation\n\nThe `FathomProvider` accepts an optional `client` prop that allows you to provide a custom Fathom client implementation. This is useful for:\n\n- **React Native apps** that need a custom tracking implementation\n- **Testing** with mock clients\n- **Server-side rendering** scenarios\n- **Custom analytics pipelines** that wrap Fathom\n\n### FathomClient Interface\n\nYour custom client must implement the `FathomClient` interface:\n\n```tsx\nimport type { FathomClient, EventOptions, LoadOptions, PageViewOptions } from 'react-fathom'\n\nconst myCustomClient: FathomClient = {\n  load: (siteId: string, options?: LoadOptions) =\u003e {\n    // Initialize your tracking\n  },\n  trackPageview: (opts?: PageViewOptions) =\u003e {\n    // Track pageview\n  },\n  trackEvent: (eventName: string, opts?: EventOptions) =\u003e {\n    // Track custom event\n  },\n  trackGoal: (code: string, cents: number) =\u003e {\n    // Track goal conversion\n  },\n  setSite: (id: string) =\u003e {\n    // Change site ID\n  },\n  blockTrackingForMe: () =\u003e {\n    // Block tracking\n  },\n  enableTrackingForMe: () =\u003e {\n    // Enable tracking\n  },\n  isTrackingEnabled: () =\u003e {\n    // Return tracking status\n    return true\n  },\n}\n```\n\n### React Native\n\nFor 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.\n\n**Install the required peer dependency:**\n\n```bash\nnpm install react-native-webview\n# or\nyarn add react-native-webview\n```\n\n**Basic setup:**\n\n```tsx\nimport { NativeFathomProvider } from 'react-fathom/native'\n\nfunction App() {\n  return (\n    \u003cNativeFathomProvider\n      siteId=\"YOUR_SITE_ID\"\n      debug={__DEV__}\n      trackAppState\n      onReady={() =\u003e console.log('Fathom ready!')}\n    \u003e\n      \u003cYourApp /\u003e\n    \u003c/NativeFathomProvider\u003e\n  )\n}\n```\n\n\u003e **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.\n\n#### React Navigation Integration\n\nTrack screen navigation as pageviews with React Navigation:\n\n```tsx\nimport { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'\nimport { NativeFathomProvider, useNavigationTracking } from 'react-fathom/native'\n\nfunction App() {\n  const navigationRef = useNavigationContainerRef()\n\n  return (\n    \u003cNativeFathomProvider siteId=\"YOUR_SITE_ID\"\u003e\n      \u003cNavigationContainer ref={navigationRef}\u003e\n        \u003cNavigationTracker navigationRef={navigationRef} /\u003e\n        \u003cRootNavigator /\u003e\n      \u003c/NavigationContainer\u003e\n    \u003c/NativeFathomProvider\u003e\n  )\n}\n\nfunction NavigationTracker({ navigationRef }) {\n  useNavigationTracking({\n    navigationRef,\n    transformRouteName: (name) =\u003e `/screens/${name}`,\n  })\n  return null\n}\n```\n\n#### App State Tracking\n\nTrack when users foreground/background your app:\n\n```tsx\nimport { useAppStateTracking } from 'react-fathom/native'\n\nfunction AppTracker() {\n  useAppStateTracking({\n    foregroundEventName: 'app-resumed',\n    backgroundEventName: 'app-paused',\n    onStateChange: (state) =\u003e console.log('App state:', state),\n  })\n  return null\n}\n```\n\n#### Using Custom Domains\n\nIf you use [Fathom's custom domains feature](https://usefathom.com/docs/script/custom-domains), specify your domain:\n\n```tsx\n\u003cNativeFathomProvider\n  siteId=\"YOUR_SITE_ID\"\n  scriptDomain=\"your-custom-domain.com\"\n\u003e\n  \u003cYourApp /\u003e\n\u003c/NativeFathomProvider\u003e\n```\n\n#### Advanced: Manual WebView Client Setup\n\nFor advanced use cases, you can manually set up the WebView client:\n\n```tsx\nimport { useRef, useMemo, useCallback } from 'react'\nimport {\n  FathomWebView,\n  createWebViewClient,\n  FathomProvider,\n  type FathomWebViewRef,\n} from 'react-fathom/native'\n\nfunction App() {\n  const webViewRef = useRef\u003cFathomWebViewRef\u003e(null)\n\n  const client = useMemo(\n    () =\u003e createWebViewClient(() =\u003e webViewRef.current, { debug: __DEV__ }),\n    []\n  )\n\n  const handleReady = useCallback(() =\u003e {\n    client.setWebViewReady()\n  }, [client])\n\n  return (\n    \u003cFathomProvider client={client} siteId=\"YOUR_SITE_ID\"\u003e\n      \u003cFathomWebView\n        ref={webViewRef}\n        siteId=\"YOUR_SITE_ID\"\n        onReady={handleReady}\n      /\u003e\n      \u003cYourApp /\u003e\n    \u003c/FathomProvider\u003e\n  )\n}\n```\n\n### Mock Client for Testing\n\n```tsx\nimport { FathomProvider, type FathomClient } from 'react-fathom'\n\nconst mockClient: FathomClient = {\n  load: jest.fn(),\n  trackPageview: jest.fn(),\n  trackEvent: jest.fn(),\n  trackGoal: jest.fn(),\n  setSite: jest.fn(),\n  blockTrackingForMe: jest.fn(),\n  enableTrackingForMe: jest.fn(),\n  isTrackingEnabled: jest.fn(() =\u003e true),\n}\n\n// In your tests\nrender(\n  \u003cFathomProvider client={mockClient}\u003e\n    \u003cComponentUnderTest /\u003e\n  \u003c/FathomProvider\u003e\n)\n\n// Assert tracking calls\nexpect(mockClient.trackEvent).toHaveBeenCalledWith('button-click', { _value: 100 })\n```\n\n## API\n\n### `FathomProvider`\n\nMain provider component for React apps. Supports composable nesting - nested providers can override `client`, `defaultPageviewOptions`, or `defaultEventOptions`.\n\n**Props:**\n\n- `siteId` (string, optional): Your Fathom Analytics site ID\n- `client` (FathomClient, optional): Custom Fathom client instance\n- `clientRef` (MutableRefObject\u003cFathomClient | null\u003e, optional): Ref that will be populated with the resolved client instance, allowing the parent component to access the client directly\n- `clientOptions` (LoadOptions, optional): Options passed to `fathom-client`\n- `defaultPageviewOptions` (PageViewOptions, optional): Default options merged into all `trackPageview` calls\n- `defaultEventOptions` (EventOptions, optional): Default options merged into all `trackEvent` calls\n\n**Example:**\n\n```tsx\n\u003cFathomProvider\n  siteId=\"YOUR_SITE_ID\"\n  defaultPageviewOptions={{ referrer: 'https://example.com' }}\n  defaultEventOptions={{ _site_id: 'global-site' }}\n\u003e\n  {/* Your app */}\n\u003c/FathomProvider\u003e\n```\n\n**Using clientRef for parent access:**\n\n```tsx\nimport { useRef } from 'react'\nimport { FathomProvider, FathomClient } from 'react-fathom'\n\nfunction App() {\n  const clientRef = useRef\u003cFathomClient\u003e(null)\n\n  const handleDeepLink = (url: string) =\u003e {\n    // Parent can track events directly via the ref\n    clientRef.current?.trackEvent('deep_link', { _url: url })\n  }\n\n  return (\n    \u003cFathomProvider siteId=\"YOUR_SITE_ID\" clientRef={clientRef}\u003e\n      \u003cYourApp onDeepLink={handleDeepLink} /\u003e\n    \u003c/FathomProvider\u003e\n  )\n}\n```\n\n### `NextFathomProviderApp`\n\nClient 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.\n\n**Props:**\n\n- `siteId` (string, optional): Your Fathom Analytics site ID\n- `client` (FathomClient, optional): Custom Fathom client instance\n- `clientOptions` (LoadOptions, optional): Options passed to `fathom-client`\n- `defaultPageviewOptions` (PageViewOptions, optional): Default options merged into all `trackPageview` calls\n- `defaultEventOptions` (EventOptions, optional): Default options merged into all `trackEvent` calls\n- `disableAutoTrack` (boolean, optional): Disable automatic pageview tracking on route changes (defaults to false)\n- `children` (ReactNode, required): Child components to render\n\n**Example:**\n\n```tsx\n// app/layout.tsx\nimport { NextFathomProviderApp } from 'react-fathom/next'\n\nexport default function RootLayout({ children }) {\n  return (\n    \u003chtml\u003e\n      \u003cbody\u003e\n        \u003cNextFathomProviderApp siteId=\"YOUR_SITE_ID\"\u003e\n          {children}\n        \u003c/NextFathomProviderApp\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n### `NextFathomTrackViewApp`\n\nComponent that tracks pageviews for Next.js App Router. Must be used within a `FathomProvider`.\n\n**Props:**\n\n- `disableAutoTrack` (boolean, optional): Disable automatic pageview tracking on route changes (defaults to false)\n\n**Example:**\n\n```tsx\n\u003cFathomProvider siteId=\"YOUR_SITE_ID\"\u003e\n  \u003cNextFathomTrackViewApp /\u003e\n  {/* Your app */}\n\u003c/FathomProvider\u003e\n```\n\n### `NextFathomTrackViewPages`\n\nComponent that tracks pageviews for Next.js Pages Router. Must be used within a `FathomProvider`.\n\n**Props:**\n\n- `disableAutoTrack` (boolean, optional): Disable automatic pageview tracking on route changes (defaults to false)\n\n**Example:**\n\n```tsx\n\u003cFathomProvider siteId=\"YOUR_SITE_ID\"\u003e\n  \u003cNextFathomTrackViewPages /\u003e\n  {/* Your app */}\n\u003c/FathomProvider\u003e\n```\n\n### `useFathom()`\n\nHook to access Fathom methods and context.\n\n**Returns:**\n\n- `trackPageview(options?)`: Track a pageview (automatically merges `defaultPageviewOptions`)\n- `trackEvent(eventName, options?)`: Track a custom event (automatically merges `defaultEventOptions`)\n- `trackGoal(code, cents)`: Track a goal conversion\n- `load(siteId, options?)`: Load Fathom with a site ID\n- `setSite(siteId)`: Change the site ID\n- `blockTrackingForMe()`: Block tracking for current user\n- `enableTrackingForMe()`: Enable tracking for current user\n- `isTrackingEnabled()`: Check if tracking is enabled\n- `client`: The Fathom client instance\n- `defaultPageviewOptions`: Current default pageview options\n- `defaultEventOptions`: Current default event options\n\n### `useTrackOnMount(options?)`\n\nHook to track a pageview when a component mounts.\n\n**Options:**\n\n- `url` (string, optional): URL to track\n- `referrer` (string, optional): Referrer URL\n- All other `PageViewOptions` from `fathom-client`\n\n### `useTrackOnClick(options)`\n\nHook that returns a click handler function to track events.\n\n**Options:**\n\n- `eventName` (string, required): Event name to track\n- `preventDefault` (boolean, optional): Whether to prevent default behavior (defaults to false)\n- `callback` ((e?: MouseEvent) =\u003e void, optional): Callback function to run after tracking\n- All other `EventOptions` from `fathom-client`\n\n### `useTrackOnVisible(options)`\n\nHook that returns a ref to attach to an element. Tracks an event when the element becomes visible.\n\n**Options:**\n\n- `eventName` (string, required): Event name to track\n- `callback` ((entry: IntersectionObserverEntry) =\u003e void, optional): Callback function to run after tracking\n- `threshold` (number, optional): IntersectionObserver threshold (defaults to 0.1)\n- `rootMargin` (string, optional): IntersectionObserver rootMargin\n- All other `EventOptions` from `fathom-client`\n\n### `TrackPageview`\n\nComponent that tracks a pageview when it mounts.\n\n**Props:**\n\n- `url` (string, optional): URL to track\n- `referrer` (string, optional): Referrer URL\n- `children` (ReactNode, optional): Child elements to render\n- All other `PageViewOptions` from `fathom-client`\n\n### `TrackClick`\n\nComponent that tracks an event when clicked.\n\n**Props:**\n\n- `eventName` (string, required): Event name to track\n- `preventDefault` (boolean, optional): Whether to prevent default behavior (defaults to false)\n- `children` (ReactNode, required): Child element(s) to render\n- All other `EventOptions` from `fathom-client`\n\n### `TrackVisible`\n\nComponent that tracks an event when it becomes visible.\n\n**Props:**\n\n- `eventName` (string, required): Event name to track\n- `threshold` (number, optional): IntersectionObserver threshold (defaults to 0.1)\n- `rootMargin` (string, optional): IntersectionObserver rootMargin\n- `children` (ReactNode, required): Child element(s) to render\n- `as` (string, optional): HTML element type to render (defaults to 'div')\n- All other `EventOptions` from `fathom-client`\n\n## Native API\n\nThe `/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).\n\n### `NativeFathomProvider`\n\nConvenience provider for React Native apps that manages a hidden WebView with Fathom's tracking script.\n\n**Props:**\n\n- `siteId` (string, required): Your Fathom Analytics site ID\n- `loadOptions` (LoadOptions, optional): Options passed to `fathom.load()` in the WebView\n- `scriptDomain` (string, optional): Custom domain for Fathom script (defaults to 'cdn.usefathom.com')\n- `defaultPageviewOptions` (PageViewOptions, optional): Default options merged into all `trackPageview` calls\n- `defaultEventOptions` (EventOptions, optional): Default options merged into all `trackEvent` calls\n- `trackAppState` (boolean, optional): Enable automatic app state tracking (defaults to false)\n- `debug` (boolean, optional): Enable debug logging (defaults to false)\n- `onReady` (() =\u003e void, optional): Called when the Fathom script has loaded\n- `onError` ((error: string) =\u003e void, optional): Called when an error occurs loading the script\n- `clientRef` (MutableRefObject\u003cWebViewFathomClient | null\u003e, 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)\n- `children` (ReactNode, required): Child components to render\n\n**Example:**\n\n```tsx\n\u003cNativeFathomProvider\n  siteId=\"YOUR_SITE_ID\"\n  debug={__DEV__}\n  trackAppState\n  onReady={() =\u003e console.log('Analytics ready!')}\n  onError={(err) =\u003e console.error('Analytics error:', err)}\n\u003e\n  \u003cApp /\u003e\n\u003c/NativeFathomProvider\u003e\n```\n\n**Using clientRef for parent access:**\n\n```tsx\nimport { useRef } from 'react'\nimport { NativeFathomProvider, WebViewFathomClient } from 'react-fathom/native'\n\nfunction App() {\n  const clientRef = useRef\u003cWebViewFathomClient\u003e(null)\n\n  const handleDeepLink = (url: string) =\u003e {\n    // Parent can track events directly via the ref\n    clientRef.current?.trackEvent('deep_link', { _url: url })\n\n    // Can also check queue status (React Native specific)\n    console.log('Queued events:', clientRef.current?.getQueueLength())\n  }\n\n  return (\n    \u003cNativeFathomProvider siteId=\"YOUR_SITE_ID\" clientRef={clientRef}\u003e\n      \u003cYourApp onDeepLink={handleDeepLink} /\u003e\n    \u003c/NativeFathomProvider\u003e\n  )\n}\n```\n\n### `FathomWebView`\n\nHidden WebView component that loads and manages the Fathom Analytics script. Used internally by `NativeFathomProvider`, but can be used directly for advanced setups.\n\n**Props:**\n\n- `siteId` (string, required): Your Fathom Analytics site ID\n- `loadOptions` (LoadOptions, optional): Options passed to `fathom.load()`\n- `scriptDomain` (string, optional): Custom domain for Fathom script (defaults to 'cdn.usefathom.com')\n- `onReady` (() =\u003e void, optional): Called when the Fathom script has loaded\n- `onError` ((error: string) =\u003e void, optional): Called when an error occurs\n- `debug` (boolean, optional): Enable debug logging (defaults to false)\n\n**Ref Methods (FathomWebViewRef):**\n\n- `trackPageview(opts?)`: Track a pageview\n- `trackEvent(eventName, opts?)`: Track a custom event\n- `trackGoal(code, cents)`: Track a goal conversion\n- `blockTrackingForMe()`: Block tracking for current user\n- `enableTrackingForMe()`: Enable tracking for current user\n- `isReady()`: Check if the WebView is ready\n\n### `createWebViewClient(getWebViewRef, options?)`\n\nFactory function to create a client that communicates with a FathomWebView.\n\n**Parameters:**\n\n- `getWebViewRef` (() =\u003e FathomWebViewRef | null): Function that returns the WebView ref\n- `options` (WebViewClientOptions, optional):\n  - `debug` (boolean): Enable debug logging (defaults to false)\n  - `enableQueue` (boolean): Enable command queuing before WebView is ready (defaults to true)\n  - `maxQueueSize` (number): Maximum commands to queue (defaults to 100)\n\n**Returns:** A `FathomClient` instance with additional methods:\n\n- `processQueue()`: Manually process queued commands (returns number of processed)\n- `getQueueLength()`: Get the current queue length\n- `setWebViewReady()`: Call when WebView signals it's ready (flushes queue)\n\n### `useAppStateTracking(options?)`\n\nHook that tracks app state changes (foreground/background) as Fathom events.\n\n**Options:**\n\n- `foregroundEventName` (string, optional): Event name for foreground (defaults to 'app-foreground')\n- `backgroundEventName` (string, optional): Event name for background (defaults to 'app-background')\n- `eventOptions` (EventOptions, optional): Additional options for app state events\n- `onStateChange` ((state: 'active' | 'background' | 'inactive') =\u003e void, optional): Callback on state change\n\n### `useNavigationTracking(options)`\n\nHook that tracks React Navigation screen changes as pageviews.\n\n**Options:**\n\n- `navigationRef` (RefObject, required): React Navigation container ref\n- `transformRouteName` ((name: string) =\u003e string, optional): Transform route names before tracking\n- `shouldTrackRoute` ((name: string, params?: object) =\u003e boolean, optional): Filter which routes to track\n- `includeParams` (boolean, optional): Include route params in tracked URL (defaults to false)\n\n**Example:**\n\n```tsx\nconst navigationRef = useNavigationContainerRef()\n\nuseNavigationTracking({\n  navigationRef,\n  transformRouteName: (name) =\u003e `/app/${name.toLowerCase()}`,\n  shouldTrackRoute: (name) =\u003e !name.startsWith('Modal'),\n  includeParams: true,\n})\n```\n\n## Tree-shaking\n\nThis library is optimized for tree-shaking. When you import only what you need:\n\n```tsx\nimport { useFathom } from 'react-fathom'\n```\n\nBundlers will automatically exclude unused code, keeping your bundle size minimal.\n\n## TypeScript\n\nFull TypeScript support is included. Types are automatically generated and exported.\n\n### Exported Types\n\nFor convenience, `react-fathom` re-exports the core types from `fathom-client` so you don't need to import from multiple packages:\n\n```tsx\nimport type {\n  // From react-fathom\n  FathomClient,\n  FathomContextInterface,\n  FathomProviderProps,\n  // Re-exported from fathom-client\n  EventOptions,\n  LoadOptions,\n  PageViewOptions,\n} from 'react-fathom'\n\n// No need for this anymore:\n// import type { EventOptions } from 'fathom-client'\n```\n\nThis simplifies your imports when building custom clients or working with typed event options.\n\n## Troubleshooting\n\n### Common Issues\n\n#### Events not appearing in Fathom dashboard\n\n**1. Verify your site ID**\n\nYour site ID should match exactly what's shown in your [Fathom dashboard](https://app.usefathom.com). It's typically an 8-character alphanumeric string like `ABCD1234`.\n\n```tsx\n// Double-check this value\n\u003cFathomProvider siteId=\"ABCD1234\"\u003e\n```\n\n**2. Check for ad blockers**\n\nMany ad blockers and privacy extensions block analytics scripts. To test:\n- Open an incognito/private window with extensions disabled\n- Or temporarily whitelist your development domain\n\n**3. Domain restrictions**\n\nFathom only tracks events from domains you've configured. For local development:\n\n```tsx\n\u003cFathomProvider\n  siteId=\"YOUR_SITE_ID\"\n  clientOptions={{\n    includedDomains: ['localhost', 'yourdomain.com']\n  }}\n\u003e\n```\n\n**4. Inspect network requests**\n\nOpen your browser's Network tab and look for requests to `cdn.usefathom.com`. If you see:\n- **No requests**: The script isn't loading (check provider setup)\n- **Blocked requests**: Ad blocker is interfering\n- **Failed requests**: Check your site ID and domain configuration\n\n#### Duplicate pageview tracking\n\nIf you're seeing double pageviews, you likely have multiple tracking setups:\n\n```tsx\n// WRONG: Both auto tracking AND manual tracking\n\u003cFathomProvider siteId=\"YOUR_SITE_ID\"\u003e\n  \u003cNextFathomTrackViewApp /\u003e {/* This tracks pageviews */}\n  {/* AND clientOptions.auto defaults to true, which also tracks */}\n\u003c/FathomProvider\u003e\n\n// CORRECT: Use one or the other\n\u003cFathomProvider siteId=\"YOUR_SITE_ID\" clientOptions={{ auto: false }}\u003e\n  \u003cNextFathomTrackViewApp /\u003e\n\u003c/FathomProvider\u003e\n```\n\n#### useFathom returns undefined methods\n\nThis 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`:\n\n1. Update to the latest version: `npm update react-fathom`\n2. Ensure your component is inside a `FathomProvider`\n\n#### Next.js App Router: \"use client\" errors\n\nServer Components can't use hooks directly. Use the pre-configured client component:\n\n```tsx\n// app/layout.tsx\nimport { NextFathomProviderApp } from 'react-fathom/next'\n\nexport default function RootLayout({ children }) {\n  return (\n    \u003chtml\u003e\n      \u003cbody\u003e\n        \u003cNextFathomProviderApp siteId=\"YOUR_SITE_ID\"\u003e\n          {children}\n        \u003c/NextFathomProviderApp\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nIf you need a custom setup, create your own client component wrapper:\n\n```tsx\n// components/AnalyticsProvider.tsx\n'use client'\nimport { FathomProvider } from 'react-fathom'\n\nexport function AnalyticsProvider({ children }) {\n  return (\n    \u003cFathomProvider siteId={process.env.NEXT_PUBLIC_FATHOM_SITE_ID}\u003e\n      {children}\n    \u003c/FathomProvider\u003e\n  )\n}\n```\n\n#### Next.js: Environment variables not loading\n\nEnsure your environment variable is prefixed with `NEXT_PUBLIC_` to be available client-side:\n\n```bash\n# .env.local\nNEXT_PUBLIC_FATHOM_SITE_ID=YOUR_SITE_ID  # ✓ Correct\nFATHOM_SITE_ID=YOUR_SITE_ID               # ✗ Won't work client-side\n```\n\n#### React Native: Events not sending\n\n**1. Verify react-native-webview is installed**\n\nThe native module requires `react-native-webview`:\n\n```bash\nnpm install react-native-webview\n# For iOS, also run:\ncd ios \u0026\u0026 pod install\n```\n\n**2. Check WebView is ready**\n\nEvents are queued until the WebView loads. Use the `onReady` callback to verify:\n\n```tsx\n\u003cNativeFathomProvider\n  siteId=\"YOUR_SITE_ID\"\n  debug={true}\n  onReady={() =\u003e console.log('Fathom WebView ready!')}\n  onError={(err) =\u003e console.error('Fathom error:', err)}\n\u003e\n```\n\n**3. Verify network connectivity**\n\nThe 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.\n\n**4. Check for WebView restrictions**\n\nSome enterprise MDM solutions or app configurations may block WebViews from loading external scripts. Verify that `cdn.usefathom.com` (or your custom domain) is accessible.\n\n**5. Debug with logging**\n\nEnable debug mode to see all tracking activity:\n\n```tsx\n\u003cNativeFathomProvider\n  siteId=\"YOUR_SITE_ID\"\n  debug={__DEV__}  // Logs all tracking calls\n\u003e\n```\n\n### Debugging Tips\n\n#### Enable verbose logging\n\nFor web, check the browser console. For React Native, enable debug mode:\n\n```tsx\n// React Native\n\u003cNativeFathomProvider\n  siteId=\"YOUR_SITE_ID\"\n  debug={__DEV__}\n\u003e\n```\n\n#### Verify tracking in real-time\n\nFathom's dashboard updates in real-time. Open your dashboard alongside your app to see events as they're tracked.\n\n#### Test with a mock client\n\nFor debugging, replace the real client with a mock that logs everything:\n\n```tsx\nconst debugClient = {\n  load: (id, opts) =\u003e console.log('load:', id, opts),\n  trackPageview: (opts) =\u003e console.log('pageview:', opts),\n  trackEvent: (name, opts) =\u003e console.log('event:', name, opts),\n  trackGoal: (code, cents) =\u003e console.log('goal:', code, cents),\n  setSite: (id) =\u003e console.log('setSite:', id),\n  blockTrackingForMe: () =\u003e console.log('blocked'),\n  enableTrackingForMe: () =\u003e console.log('enabled'),\n  isTrackingEnabled: () =\u003e true,\n}\n\n\u003cFathomProvider client={debugClient}\u003e\n```\n\n### Getting Help\n\n- [Open an issue](https://github.com/ryanhefner/react-fathom/issues) on GitHub\n- [Search existing issues](https://github.com/ryanhefner/react-fathom/issues?q=is%3Aissue) for solutions\n- [Fathom Analytics documentation](https://usefathom.com/docs) for platform-specific questions\n\n## Contributing\n\nContributions are welcome! Whether it's bug fixes, new features, documentation improvements, or examples, we appreciate your help.\n\n### Ways to Contribute\n\n| Type | Description |\n|------|-------------|\n| **Bug Reports** | Found a bug? [Open an issue](https://github.com/ryanhefner/react-fathom/issues/new) with reproduction steps |\n| **Feature Requests** | Have an idea? Discuss it in an issue first |\n| **Bug Fixes** | PRs for documented issues are always welcome |\n| **Documentation** | Help improve docs, add examples, fix typos |\n| **Tests** | Increase test coverage or add edge case tests |\n\n### Development Setup\n\n**Prerequisites:**\n- Node.js 18+\n- npm 9+\n\n**1. Clone and install:**\n\n```bash\ngit clone https://github.com/ryanhefner/react-fathom.git\ncd react-fathom\nnpm install\n```\n\n**2. Run the development workflow:**\n\n```bash\n# Run tests in watch mode during development\nnpm run test:watch\n\n# Run the full test suite\nnpm test\n\n# Build the package\nnpm run build\n\n# Type check without emitting\nnpm run typecheck\n```\n\n### Project Structure\n\n```\nreact-fathom/\n├── src/\n│   ├── index.ts              # Main entry point\n│   ├── FathomProvider.tsx    # Core provider component\n│   ├── FathomContext.tsx     # React context\n│   ├── types.ts              # TypeScript definitions\n│   ├── hooks/                # React hooks\n│   │   ├── useFathom.ts\n│   │   ├── useTrackOnClick.ts\n│   │   ├── useTrackOnMount.ts\n│   │   └── useTrackOnVisible.ts\n│   ├── components/           # Declarative tracking components\n│   │   ├── TrackClick.tsx\n│   │   ├── TrackPageview.tsx\n│   │   └── TrackVisible.tsx\n│   ├── next/                 # Next.js-specific exports\n│   │   └── index.ts\n│   └── native/               # React Native exports\n│       ├── index.ts\n│       ├── FathomWebView.tsx\n│       ├── createWebViewClient.ts\n│       ├── NativeFathomProvider.tsx\n│       ├── useNavigationTracking.ts\n│       └── useAppStateTracking.ts\n├── examples/                 # Example applications\n│   ├── next-app/            # Next.js App Router example\n│   └── next-pages/          # Next.js Pages Router example\n└── dist/                    # Built output (generated)\n```\n\n### Testing Guidelines\n\nWe use [Vitest](https://vitest.dev/) for testing. All new features should include tests.\n\n```bash\n# Run all tests\nnpm test\n\n# Run tests in watch mode\nnpm run test:watch\n\n# Run tests with coverage\nnpm run test:coverage\n```\n\n**Writing tests:**\n\n```tsx\n// src/hooks/useMyHook.test.tsx\nimport { renderHook } from '@testing-library/react'\nimport { describe, it, expect, vi } from 'vitest'\nimport { useMyHook } from './useMyHook'\nimport { FathomProvider } from '../FathomProvider'\n\ndescribe('useMyHook', () =\u003e {\n  it('should track events correctly', () =\u003e {\n    const mockClient = {\n      trackEvent: vi.fn(),\n      // ... other required methods\n    }\n\n    const wrapper = ({ children }) =\u003e (\n      \u003cFathomProvider client={mockClient}\u003e{children}\u003c/FathomProvider\u003e\n    )\n\n    const { result } = renderHook(() =\u003e useMyHook(), { wrapper })\n\n    result.current.doSomething()\n\n    expect(mockClient.trackEvent).toHaveBeenCalledWith('expected-event', {})\n  })\n})\n```\n\n### Code Style\n\n- **TypeScript**: All code should be fully typed\n- **Formatting**: We use Prettier (run `npm run format` before committing)\n- **Linting**: ESLint catches common issues (run `npm run lint`)\n- **Naming**:\n  - Components: PascalCase (`TrackClick.tsx`)\n  - Hooks: camelCase with `use` prefix (`useFathom.ts`)\n  - Types: PascalCase (`FathomClient`)\n\n### Submitting a Pull Request\n\n1. **Fork** the repository\n2. **Create a branch** from `main`:\n   ```bash\n   git checkout -b fix/my-bug-fix\n   # or\n   git checkout -b feature/my-new-feature\n   ```\n3. **Make your changes** with clear, focused commits\n4. **Add or update tests** for your changes\n5. **Ensure CI passes**:\n   ```bash\n   npm run lint\n   npm test\n   npm run build\n   ```\n6. **Submit a PR** with a clear description of what and why\n\n### Commit Message Guidelines\n\nUse clear, descriptive commit messages:\n\n```\nfeat: add useTrackOnScroll hook for scroll tracking\nfix: resolve duplicate pageview tracking in Next.js\ndocs: add troubleshooting section for ad blockers\ntest: add tests for native offline queue\nrefactor: simplify FathomContext default values\n```\n\nPrefixes: `feat`, `fix`, `docs`, `test`, `refactor`, `chore`, `perf`\n\n## License\n\n[MIT](LICENSE) © [Ryan Hefner](https://www.ryanhefner.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanhefner%2Freact-fathom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryanhefner%2Freact-fathom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanhefner%2Freact-fathom/lists"}