react-fathom 0.1.11 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +886 -24
  2. package/dist/cjs/index.cjs +55 -9
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/cjs/native/index.cjs +1079 -0
  5. package/dist/cjs/native/index.cjs.map +1 -0
  6. package/dist/cjs/next/index.cjs +51 -5
  7. package/dist/cjs/next/index.cjs.map +1 -1
  8. package/dist/es/index.js +55 -9
  9. package/dist/es/index.js.map +1 -1
  10. package/dist/es/native/index.js +1071 -0
  11. package/dist/es/native/index.js.map +1 -0
  12. package/dist/es/next/index.js +51 -5
  13. package/dist/es/next/index.js.map +1 -1
  14. package/dist/react-fathom.js +55 -9
  15. package/dist/react-fathom.js.map +1 -1
  16. package/dist/react-fathom.min.js +2 -2
  17. package/dist/react-fathom.min.js.map +1 -1
  18. package/package.json +27 -4
  19. package/src/FathomContext.tsx +30 -1
  20. package/src/FathomProvider.test.tsx +115 -15
  21. package/src/FathomProvider.tsx +10 -2
  22. package/src/components/TrackClick.test.tsx +7 -7
  23. package/src/components/TrackClick.tsx +1 -1
  24. package/src/components/TrackVisible.test.tsx +7 -7
  25. package/src/components/TrackVisible.tsx +1 -1
  26. package/src/hooks/useFathom.test.tsx +14 -3
  27. package/src/hooks/useTrackOnClick.test.tsx +4 -4
  28. package/src/hooks/useTrackOnClick.ts +1 -1
  29. package/src/hooks/useTrackOnVisible.test.tsx +4 -4
  30. package/src/hooks/useTrackOnVisible.ts +1 -1
  31. package/src/index.ts +1 -0
  32. package/src/native/FathomWebView.test.tsx +410 -0
  33. package/src/native/FathomWebView.tsx +297 -0
  34. package/src/native/NativeFathomProvider.test.tsx +372 -0
  35. package/src/native/NativeFathomProvider.tsx +113 -0
  36. package/src/native/createWebViewClient.test.ts +380 -0
  37. package/src/native/createWebViewClient.ts +271 -0
  38. package/src/native/index.ts +29 -0
  39. package/src/native/react-native.d.ts +74 -0
  40. package/src/native/types.ts +145 -0
  41. package/src/native/useAppStateTracking.test.ts +249 -0
  42. package/src/native/useAppStateTracking.ts +66 -0
  43. package/src/native/useNavigationTracking.test.ts +446 -0
  44. package/src/native/useNavigationTracking.ts +177 -0
  45. package/src/types.ts +36 -9
  46. package/types/FathomContext.d.ts +1 -1
  47. package/types/FathomContext.d.ts.map +1 -1
  48. package/types/FathomProvider.d.ts.map +1 -1
  49. package/types/components/TrackClick.d.ts +1 -1
  50. package/types/components/TrackVisible.d.ts +1 -1
  51. package/types/hooks/useTrackOnClick.d.ts +1 -1
  52. package/types/hooks/useTrackOnVisible.d.ts +1 -1
  53. package/types/index.d.ts +1 -0
  54. package/types/index.d.ts.map +1 -1
  55. package/types/native/FathomWebView.d.ts +59 -0
  56. package/types/native/FathomWebView.d.ts.map +1 -0
  57. package/types/native/NativeFathomProvider.d.ts +36 -0
  58. package/types/native/NativeFathomProvider.d.ts.map +1 -0
  59. package/types/native/createWebViewClient.d.ts +51 -0
  60. package/types/native/createWebViewClient.d.ts.map +1 -0
  61. package/types/native/index.d.ts +10 -0
  62. package/types/native/index.d.ts.map +1 -0
  63. package/types/native/types.d.ts +125 -0
  64. package/types/native/types.d.ts.map +1 -0
  65. package/types/native/useAppStateTracking.d.ts +25 -0
  66. package/types/native/useAppStateTracking.d.ts.map +1 -0
  67. package/types/native/useNavigationTracking.d.ts +30 -0
  68. package/types/native/useNavigationTracking.d.ts.map +1 -0
  69. package/types/types.d.ts +34 -9
  70. package/types/types.d.ts.map +1 -1
package/README.md CHANGED
@@ -1,36 +1,97 @@
1
- # 😻 react-fathom
1
+ # react-fathom
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/react-fathom?style=flat-square)](https://www.pkgstats.com/pkg:react-fathom)
4
4
  [![NPM](https://img.shields.io/npm/l/react-fathom?style=flat-square)](LICENSE)
5
5
  [![npm](https://img.shields.io/npm/dt/react-fathom?style=flat-square)](https://www.pkgstats.com/pkg:react-fathom)
6
6
  [![npm bundle size](https://img.shields.io/bundlephobia/minzip/react-fathom?style=flat-square)](https://bundlephobia.com/package/react-fathom)
7
7
  [![GitHub stars](https://img.shields.io/github/stars/ryanhefner/react-fathom?style=flat-square)](https://github.com/ryanhefner/react-fathom/stargazers)
8
- [![GitHub forks](https://img.shields.io/github/forks/ryanhefner/react-fathom?style=flat-square)](https://github.com/ryanhefner/react-fathom/network/members)
9
- [![GitHub issues](https://img.shields.io/github/issues/ryanhefner/react-fathom?style=flat-square)](https://github.com/ryanhefner/react-fathom/issues)
10
- [![GitHub pull requests](https://img.shields.io/github/issues-pr/ryanhefner/react-fathom?style=flat-square)](https://github.com/ryanhefner/react-fathom/pulls)
11
- [![Coveralls github](https://img.shields.io/coveralls/github/ryanhefner/react-fathom?style=flat-square)](https://coveralls.io/github/ryanhefner/react-fathom)
12
- [![codecov](https://codecov.io/gh/ryanhefner/react-fathom/branch/main/graph/badge.svg)](https://codecov.io/gh/ryanhefner/react-fathom)
13
- [![CircleCI](https://img.shields.io/circleci/build/github/ryanhefner/react-fathom?style=flat-square)](https://circleci.com/gh/ryanhefner/react-fathom)
14
- [![Known Vulnerabilities](https://snyk.io/test/github/ryanhefner/react-fathom/badge.svg)](https://snyk.io/test/github/ryanhefner/react-fathom)
15
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/)
16
- [![GitHub last commit](https://img.shields.io/github/last-commit/ryanhefner/react-fathom?style=flat-square)](https://github.com/ryanhefner/react-fathom/commits/main)
17
- [![Twitter Follow](https://img.shields.io/twitter/follow/ryanhefner?style=flat-square)](https://twitter.com/ryanhefner)
9
+ [![codecov](https://codecov.io/gh/ryanhefner/react-fathom/branch/main/graph/badge.svg)](https://codecov.io/gh/ryanhefner/react-fathom)
10
+
11
+ **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.
12
+
13
+ ## Table of Contents
14
+
15
+ - [Quick Start](#quick-start)
16
+ - [Why react-fathom?](#why-react-fathom)
17
+ - [Features](#features)
18
+ - [Installation](#install)
19
+ - [Usage](#usage)
20
+ - [Basic React Setup](#basic-react-setup)
21
+ - [Next.js App Router](#nextjs-app-router)
22
+ - [Next.js Pages Router](#nextjs-pages-router)
23
+ - [React Native](#react-native)
24
+ - [API Reference](#api)
25
+ - [Troubleshooting](#troubleshooting)
26
+ - [Contributing](#contributing)
27
+ - [License](#license)
28
+
29
+ ## Quick Start
30
+
31
+ ```bash
32
+ npm install react-fathom fathom-client
33
+ ```
34
+
35
+ ```tsx
36
+ // App.tsx or layout.tsx
37
+ import { FathomProvider } from 'react-fathom'
38
+
39
+ function App() {
40
+ return (
41
+ <FathomProvider siteId="YOUR_FATHOM_SITE_ID">
42
+ <YourApp />
43
+ </FathomProvider>
44
+ )
45
+ }
46
+ ```
47
+
48
+ ```tsx
49
+ // Any component
50
+ import { useFathom } from 'react-fathom'
51
+
52
+ function MyComponent() {
53
+ const { trackEvent } = useFathom()
54
+
55
+ return (
56
+ <button onClick={() => trackEvent('button-click')}>
57
+ Click me
58
+ </button>
59
+ )
60
+ }
61
+ ```
18
62
 
19
- Easily compose Fathom Analytics into your React/Next.js apps with automatic pageview tracking and full TypeScript support.
63
+ That's it! Pageviews are tracked automatically.
20
64
 
21
- ## About Fathom Analytics
65
+ ## Why react-fathom?
22
66
 
23
- This package is designed to work with [Fathom Analytics](https://usefathom.com/ref/EKONBS), a privacy-first website analytics platform. Fathom provides simple, GDPR-compliant analytics without cookies or tracking scripts that invade user privacy.
67
+ ### Privacy-First Analytics
24
68
 
25
- **New to Fathom?** Get a **$10 credit** on your first invoice when you sign up using [this affiliate link](https://usefathom.com/ref/EKONBS). This helps support the development of this open-source package.
69
+ [Fathom Analytics](https://usefathom.com/ref/EKONBS) is a privacy-focused alternative to Google Analytics. Unlike traditional analytics platforms:
70
+
71
+ - **No cookies required** - GDPR, CCPA, and PECR compliant out of the box
72
+ - **No personal data collection** - Respects user privacy by design
73
+ - **No consent banners needed** - Simplified compliance for your websites
74
+ - **Fast and lightweight** - Won't slow down your site
75
+
76
+ ### Why Use This Package?
77
+
78
+ - **React-native integration** - Works seamlessly with React's component model and hooks
79
+ - **Automatic tracking** - Pageviews tracked automatically on route changes
80
+ - **Next.js optimized** - First-class support for both App Router and Pages Router
81
+ - **React Native support** - Full mobile support with offline queuing
82
+ - **TypeScript-first** - Complete type definitions for a great developer experience
83
+ - **Tree-shakeable** - Only bundle what you use
84
+
85
+ **New to Fathom?** Get a **$10 credit** when you sign up using [this referral link](https://usefathom.com/ref/EKONBS).
26
86
 
27
87
  ## Features
28
88
 
29
89
  - 🚀 **Zero-config** Fathom Analytics integration for React
30
90
  - 📦 **Tree-shakeable** - Only bundle what you use
31
91
  - 🔄 **Automatic pageview tracking** for Next.js (Pages Router & App Router)
92
+ - 📱 **React Native support** with offline queuing and navigation tracking
32
93
  - 💪 **Full TypeScript** support with type definitions
33
- - 🎯 **Flexible** - Works with any React app or Next.js
94
+ - 🎯 **Flexible** - Works with any React app, Next.js, or React Native
34
95
  - ⚡ **Lightweight** - Minimal bundle size impact
35
96
 
36
97
  ## Install
@@ -50,9 +111,11 @@ yarn add react-fathom fathom-client
50
111
  ## Peer Dependencies
51
112
 
52
113
  - `react` >= 16.8
53
- - `react-dom` >= 16.8
54
- - `fathom-client` >= 3.0.0
114
+ - `react-dom` >= 16.8 (only if using web)
115
+ - `fathom-client` >= 3.0.0 (only if using web, not needed for React Native)
55
116
  - `next` >= 10.0.0 (only if using Next.js providers)
117
+ - `react-native` >= 0.60.0 (only if using React Native)
118
+ - `react-native-webview` >= 11.0.0 (only if using React Native)
56
119
 
57
120
  ## Usage
58
121
 
@@ -79,11 +142,11 @@ function MyComponent() {
79
142
  const { trackPageview, trackEvent, trackGoal, load } = useFathom()
80
143
 
81
144
  const handleClick = () => {
82
- trackEvent?.('button-click', { id: 'signup-button' })
145
+ trackEvent('button-click', { _value: 100 }) // Optional: value in cents
83
146
  }
84
147
 
85
148
  const handlePurchase = () => {
86
- trackGoal?.('purchase', 2999) // $29.99 in cents
149
+ trackGoal('purchase', 2999) // $29.99 in cents
87
150
  }
88
151
 
89
152
  return (
@@ -113,7 +176,7 @@ function MyComponent() {
113
176
  // Track event on click
114
177
  const handleClick = useTrackOnClick({
115
178
  eventName: 'button-click',
116
- id: 'signup-button',
179
+ _value: 100, // Optional: value in cents
117
180
  callback: (e) => {
118
181
  console.log('Tracked click!', e)
119
182
  },
@@ -122,7 +185,7 @@ function MyComponent() {
122
185
  // Track event when element becomes visible
123
186
  const ref = useTrackOnVisible({
124
187
  eventName: 'section-viewed',
125
- section: 'hero',
188
+ _value: 1, // Optional: value in cents
126
189
  callback: (entry) => {
127
190
  console.log('Element is visible!', entry)
128
191
  },
@@ -153,12 +216,12 @@ function MyPage() {
153
216
  </TrackPageview>
154
217
 
155
218
  {/* Track click events */}
156
- <TrackClick eventName="button-click" id="signup-button">
219
+ <TrackClick eventName="button-click" _value={100}>
157
220
  <button>Sign Up</button>
158
221
  </TrackClick>
159
222
 
160
223
  {/* Track when element becomes visible */}
161
- <TrackVisible eventName="section-viewed" section="hero">
224
+ <TrackVisible eventName="section-viewed" _value={1}>
162
225
  <div>Hero section</div>
163
226
  </TrackVisible>
164
227
  </>
@@ -231,6 +294,261 @@ function MyApp({ Component, pageProps }) {
231
294
  export default MyApp
232
295
  ```
233
296
 
297
+ ## Default Options Merging
298
+
299
+ The `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.
300
+
301
+ ### How Merging Works
302
+
303
+ Default options are spread first, then any options you pass to individual tracking calls are spread second. This means:
304
+
305
+ - **Default options** provide base values for all tracking calls
306
+ - **Provided options** override defaults when specified
307
+ - You can set defaults once and forget about them
308
+
309
+ ```tsx
310
+ <FathomProvider
311
+ siteId="YOUR_SITE_ID"
312
+ defaultEventOptions={{ _site_id: 'my-app' }}
313
+ >
314
+ {/* All trackEvent calls will include _site_id: 'my-app' unless overridden */}
315
+ </FathomProvider>
316
+ ```
317
+
318
+ ```tsx
319
+ // Inside your component
320
+ const { trackEvent } = useFathom()
321
+
322
+ // Uses default: { _site_id: 'my-app' }
323
+ trackEvent('button-click')
324
+
325
+ // Merges with default: { _site_id: 'my-app', _value: 100 }
326
+ trackEvent('purchase', { _value: 100 })
327
+
328
+ // Overrides default: { _site_id: 'custom-site', _value: 50 }
329
+ trackEvent('special-event', { _site_id: 'custom-site', _value: 50 })
330
+ ```
331
+
332
+ ### Nested Providers
333
+
334
+ When nesting `FathomProvider` components, child providers inherit defaults from their parent but can override them:
335
+
336
+ ```tsx
337
+ <FathomProvider
338
+ siteId="YOUR_SITE_ID"
339
+ defaultEventOptions={{ _site_id: 'global' }}
340
+ >
341
+ {/* Events here use _site_id: 'global' */}
342
+
343
+ <FathomProvider defaultEventOptions={{ _site_id: 'dashboard' }}>
344
+ {/* Events here use _site_id: 'dashboard' */}
345
+ </FathomProvider>
346
+ </FathomProvider>
347
+ ```
348
+
349
+ ## Custom Client Implementation
350
+
351
+ The `FathomProvider` accepts an optional `client` prop that allows you to provide a custom Fathom client implementation. This is useful for:
352
+
353
+ - **React Native apps** that need a custom tracking implementation
354
+ - **Testing** with mock clients
355
+ - **Server-side rendering** scenarios
356
+ - **Custom analytics pipelines** that wrap Fathom
357
+
358
+ ### FathomClient Interface
359
+
360
+ Your custom client must implement the `FathomClient` interface:
361
+
362
+ ```tsx
363
+ import type { FathomClient, EventOptions, LoadOptions, PageViewOptions } from 'react-fathom'
364
+
365
+ const myCustomClient: FathomClient = {
366
+ load: (siteId: string, options?: LoadOptions) => {
367
+ // Initialize your tracking
368
+ },
369
+ trackPageview: (opts?: PageViewOptions) => {
370
+ // Track pageview
371
+ },
372
+ trackEvent: (eventName: string, opts?: EventOptions) => {
373
+ // Track custom event
374
+ },
375
+ trackGoal: (code: string, cents: number) => {
376
+ // Track goal conversion
377
+ },
378
+ setSite: (id: string) => {
379
+ // Change site ID
380
+ },
381
+ blockTrackingForMe: () => {
382
+ // Block tracking
383
+ },
384
+ enableTrackingForMe: () => {
385
+ // Enable tracking
386
+ },
387
+ isTrackingEnabled: () => {
388
+ // Return tracking status
389
+ return true
390
+ },
391
+ }
392
+ ```
393
+
394
+ ### React Native
395
+
396
+ 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.
397
+
398
+ **Install the required peer dependency:**
399
+
400
+ ```bash
401
+ npm install react-native-webview
402
+ # or
403
+ yarn add react-native-webview
404
+ ```
405
+
406
+ **Basic setup:**
407
+
408
+ ```tsx
409
+ import { NativeFathomProvider } from 'react-fathom/native'
410
+
411
+ function App() {
412
+ return (
413
+ <NativeFathomProvider
414
+ siteId="YOUR_SITE_ID"
415
+ debug={__DEV__}
416
+ trackAppState
417
+ onReady={() => console.log('Fathom ready!')}
418
+ >
419
+ <YourApp />
420
+ </NativeFathomProvider>
421
+ )
422
+ }
423
+ ```
424
+
425
+ > **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.
426
+
427
+ #### React Navigation Integration
428
+
429
+ Track screen navigation as pageviews with React Navigation:
430
+
431
+ ```tsx
432
+ import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'
433
+ import { NativeFathomProvider, useNavigationTracking } from 'react-fathom/native'
434
+
435
+ function App() {
436
+ const navigationRef = useNavigationContainerRef()
437
+
438
+ return (
439
+ <NativeFathomProvider siteId="YOUR_SITE_ID">
440
+ <NavigationContainer ref={navigationRef}>
441
+ <NavigationTracker navigationRef={navigationRef} />
442
+ <RootNavigator />
443
+ </NavigationContainer>
444
+ </NativeFathomProvider>
445
+ )
446
+ }
447
+
448
+ function NavigationTracker({ navigationRef }) {
449
+ useNavigationTracking({
450
+ navigationRef,
451
+ transformRouteName: (name) => `/screens/${name}`,
452
+ })
453
+ return null
454
+ }
455
+ ```
456
+
457
+ #### App State Tracking
458
+
459
+ Track when users foreground/background your app:
460
+
461
+ ```tsx
462
+ import { useAppStateTracking } from 'react-fathom/native'
463
+
464
+ function AppTracker() {
465
+ useAppStateTracking({
466
+ foregroundEventName: 'app-resumed',
467
+ backgroundEventName: 'app-paused',
468
+ onStateChange: (state) => console.log('App state:', state),
469
+ })
470
+ return null
471
+ }
472
+ ```
473
+
474
+ #### Using Custom Domains
475
+
476
+ If you use [Fathom's custom domains feature](https://usefathom.com/docs/script/custom-domains), specify your domain:
477
+
478
+ ```tsx
479
+ <NativeFathomProvider
480
+ siteId="YOUR_SITE_ID"
481
+ scriptDomain="your-custom-domain.com"
482
+ >
483
+ <YourApp />
484
+ </NativeFathomProvider>
485
+ ```
486
+
487
+ #### Advanced: Manual WebView Client Setup
488
+
489
+ For advanced use cases, you can manually set up the WebView client:
490
+
491
+ ```tsx
492
+ import { useRef, useMemo, useCallback } from 'react'
493
+ import {
494
+ FathomWebView,
495
+ createWebViewClient,
496
+ FathomProvider,
497
+ type FathomWebViewRef,
498
+ } from 'react-fathom/native'
499
+
500
+ function App() {
501
+ const webViewRef = useRef<FathomWebViewRef>(null)
502
+
503
+ const client = useMemo(
504
+ () => createWebViewClient(() => webViewRef.current, { debug: __DEV__ }),
505
+ []
506
+ )
507
+
508
+ const handleReady = useCallback(() => {
509
+ client.setWebViewReady()
510
+ }, [client])
511
+
512
+ return (
513
+ <FathomProvider client={client} siteId="YOUR_SITE_ID">
514
+ <FathomWebView
515
+ ref={webViewRef}
516
+ siteId="YOUR_SITE_ID"
517
+ onReady={handleReady}
518
+ />
519
+ <YourApp />
520
+ </FathomProvider>
521
+ )
522
+ }
523
+ ```
524
+
525
+ ### Mock Client for Testing
526
+
527
+ ```tsx
528
+ import { FathomProvider, type FathomClient } from 'react-fathom'
529
+
530
+ const mockClient: FathomClient = {
531
+ load: jest.fn(),
532
+ trackPageview: jest.fn(),
533
+ trackEvent: jest.fn(),
534
+ trackGoal: jest.fn(),
535
+ setSite: jest.fn(),
536
+ blockTrackingForMe: jest.fn(),
537
+ enableTrackingForMe: jest.fn(),
538
+ isTrackingEnabled: jest.fn(() => true),
539
+ }
540
+
541
+ // In your tests
542
+ render(
543
+ <FathomProvider client={mockClient}>
544
+ <ComponentUnderTest />
545
+ </FathomProvider>
546
+ )
547
+
548
+ // Assert tracking calls
549
+ expect(mockClient.trackEvent).toHaveBeenCalledWith('button-click', { _value: 100 })
550
+ ```
551
+
234
552
  ## API
235
553
 
236
554
  ### `FathomProvider`
@@ -241,6 +559,7 @@ Main provider component for React apps. Supports composable nesting - nested pro
241
559
 
242
560
  - `siteId` (string, optional): Your Fathom Analytics site ID
243
561
  - `client` (FathomClient, optional): Custom Fathom client instance
562
+ - `clientRef` (MutableRefObject<FathomClient | null>, optional): Ref that will be populated with the resolved client instance, allowing the parent component to access the client directly
244
563
  - `clientOptions` (LoadOptions, optional): Options passed to `fathom-client`
245
564
  - `defaultPageviewOptions` (PageViewOptions, optional): Default options merged into all `trackPageview` calls
246
565
  - `defaultEventOptions` (EventOptions, optional): Default options merged into all `trackEvent` calls
@@ -251,12 +570,34 @@ Main provider component for React apps. Supports composable nesting - nested pro
251
570
  <FathomProvider
252
571
  siteId="YOUR_SITE_ID"
253
572
  defaultPageviewOptions={{ referrer: 'https://example.com' }}
254
- defaultEventOptions={{ id: 'global-id' }}
573
+ defaultEventOptions={{ _site_id: 'global-site' }}
255
574
  >
256
575
  {/* Your app */}
257
576
  </FathomProvider>
258
577
  ```
259
578
 
579
+ **Using clientRef for parent access:**
580
+
581
+ ```tsx
582
+ import { useRef } from 'react'
583
+ import { FathomProvider, FathomClient } from 'react-fathom'
584
+
585
+ function App() {
586
+ const clientRef = useRef<FathomClient>(null)
587
+
588
+ const handleDeepLink = (url: string) => {
589
+ // Parent can track events directly via the ref
590
+ clientRef.current?.trackEvent('deep_link', { _url: url })
591
+ }
592
+
593
+ return (
594
+ <FathomProvider siteId="YOUR_SITE_ID" clientRef={clientRef}>
595
+ <YourApp onDeepLink={handleDeepLink} />
596
+ </FathomProvider>
597
+ )
598
+ }
599
+ ```
600
+
260
601
  ### `NextFathomProviderApp`
261
602
 
262
603
  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.
@@ -410,6 +751,142 @@ Component that tracks an event when it becomes visible.
410
751
  - `as` (string, optional): HTML element type to render (defaults to 'div')
411
752
  - All other `EventOptions` from `fathom-client`
412
753
 
754
+ ## Native API
755
+
756
+ 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).
757
+
758
+ ### `NativeFathomProvider`
759
+
760
+ Convenience provider for React Native apps that manages a hidden WebView with Fathom's tracking script.
761
+
762
+ **Props:**
763
+
764
+ - `siteId` (string, required): Your Fathom Analytics site ID
765
+ - `loadOptions` (LoadOptions, optional): Options passed to `fathom.load()` in the WebView
766
+ - `scriptDomain` (string, optional): Custom domain for Fathom script (defaults to 'cdn.usefathom.com')
767
+ - `defaultPageviewOptions` (PageViewOptions, optional): Default options merged into all `trackPageview` calls
768
+ - `defaultEventOptions` (EventOptions, optional): Default options merged into all `trackEvent` calls
769
+ - `trackAppState` (boolean, optional): Enable automatic app state tracking (defaults to false)
770
+ - `debug` (boolean, optional): Enable debug logging (defaults to false)
771
+ - `onReady` (() => void, optional): Called when the Fathom script has loaded
772
+ - `onError` ((error: string) => void, optional): Called when an error occurs loading the script
773
+ - `clientRef` (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)
774
+ - `children` (ReactNode, required): Child components to render
775
+
776
+ **Example:**
777
+
778
+ ```tsx
779
+ <NativeFathomProvider
780
+ siteId="YOUR_SITE_ID"
781
+ debug={__DEV__}
782
+ trackAppState
783
+ onReady={() => console.log('Analytics ready!')}
784
+ onError={(err) => console.error('Analytics error:', err)}
785
+ >
786
+ <App />
787
+ </NativeFathomProvider>
788
+ ```
789
+
790
+ **Using clientRef for parent access:**
791
+
792
+ ```tsx
793
+ import { useRef } from 'react'
794
+ import { NativeFathomProvider, WebViewFathomClient } from 'react-fathom/native'
795
+
796
+ function App() {
797
+ const clientRef = useRef<WebViewFathomClient>(null)
798
+
799
+ const handleDeepLink = (url: string) => {
800
+ // Parent can track events directly via the ref
801
+ clientRef.current?.trackEvent('deep_link', { _url: url })
802
+
803
+ // Can also check queue status (React Native specific)
804
+ console.log('Queued events:', clientRef.current?.getQueueLength())
805
+ }
806
+
807
+ return (
808
+ <NativeFathomProvider siteId="YOUR_SITE_ID" clientRef={clientRef}>
809
+ <YourApp onDeepLink={handleDeepLink} />
810
+ </NativeFathomProvider>
811
+ )
812
+ }
813
+ ```
814
+
815
+ ### `FathomWebView`
816
+
817
+ Hidden WebView component that loads and manages the Fathom Analytics script. Used internally by `NativeFathomProvider`, but can be used directly for advanced setups.
818
+
819
+ **Props:**
820
+
821
+ - `siteId` (string, required): Your Fathom Analytics site ID
822
+ - `loadOptions` (LoadOptions, optional): Options passed to `fathom.load()`
823
+ - `scriptDomain` (string, optional): Custom domain for Fathom script (defaults to 'cdn.usefathom.com')
824
+ - `onReady` (() => void, optional): Called when the Fathom script has loaded
825
+ - `onError` ((error: string) => void, optional): Called when an error occurs
826
+ - `debug` (boolean, optional): Enable debug logging (defaults to false)
827
+
828
+ **Ref Methods (FathomWebViewRef):**
829
+
830
+ - `trackPageview(opts?)`: Track a pageview
831
+ - `trackEvent(eventName, opts?)`: Track a custom event
832
+ - `trackGoal(code, cents)`: Track a goal conversion
833
+ - `blockTrackingForMe()`: Block tracking for current user
834
+ - `enableTrackingForMe()`: Enable tracking for current user
835
+ - `isReady()`: Check if the WebView is ready
836
+
837
+ ### `createWebViewClient(getWebViewRef, options?)`
838
+
839
+ Factory function to create a client that communicates with a FathomWebView.
840
+
841
+ **Parameters:**
842
+
843
+ - `getWebViewRef` (() => FathomWebViewRef | null): Function that returns the WebView ref
844
+ - `options` (WebViewClientOptions, optional):
845
+ - `debug` (boolean): Enable debug logging (defaults to false)
846
+ - `enableQueue` (boolean): Enable command queuing before WebView is ready (defaults to true)
847
+ - `maxQueueSize` (number): Maximum commands to queue (defaults to 100)
848
+
849
+ **Returns:** A `FathomClient` instance with additional methods:
850
+
851
+ - `processQueue()`: Manually process queued commands (returns number of processed)
852
+ - `getQueueLength()`: Get the current queue length
853
+ - `setWebViewReady()`: Call when WebView signals it's ready (flushes queue)
854
+
855
+ ### `useAppStateTracking(options?)`
856
+
857
+ Hook that tracks app state changes (foreground/background) as Fathom events.
858
+
859
+ **Options:**
860
+
861
+ - `foregroundEventName` (string, optional): Event name for foreground (defaults to 'app-foreground')
862
+ - `backgroundEventName` (string, optional): Event name for background (defaults to 'app-background')
863
+ - `eventOptions` (EventOptions, optional): Additional options for app state events
864
+ - `onStateChange` ((state: 'active' | 'background' | 'inactive') => void, optional): Callback on state change
865
+
866
+ ### `useNavigationTracking(options)`
867
+
868
+ Hook that tracks React Navigation screen changes as pageviews.
869
+
870
+ **Options:**
871
+
872
+ - `navigationRef` (RefObject, required): React Navigation container ref
873
+ - `transformRouteName` ((name: string) => string, optional): Transform route names before tracking
874
+ - `shouldTrackRoute` ((name: string, params?: object) => boolean, optional): Filter which routes to track
875
+ - `includeParams` (boolean, optional): Include route params in tracked URL (defaults to false)
876
+
877
+ **Example:**
878
+
879
+ ```tsx
880
+ const navigationRef = useNavigationContainerRef()
881
+
882
+ useNavigationTracking({
883
+ navigationRef,
884
+ transformRouteName: (name) => `/app/${name.toLowerCase()}`,
885
+ shouldTrackRoute: (name) => !name.startsWith('Modal'),
886
+ includeParams: true,
887
+ })
888
+ ```
889
+
413
890
  ## Tree-shaking
414
891
 
415
892
  This library is optimized for tree-shaking. When you import only what you need:
@@ -424,6 +901,391 @@ Bundlers will automatically exclude unused code, keeping your bundle size minima
424
901
 
425
902
  Full TypeScript support is included. Types are automatically generated and exported.
426
903
 
904
+ ### Exported Types
905
+
906
+ For convenience, `react-fathom` re-exports the core types from `fathom-client` so you don't need to import from multiple packages:
907
+
908
+ ```tsx
909
+ import type {
910
+ // From react-fathom
911
+ FathomClient,
912
+ FathomContextInterface,
913
+ FathomProviderProps,
914
+ // Re-exported from fathom-client
915
+ EventOptions,
916
+ LoadOptions,
917
+ PageViewOptions,
918
+ } from 'react-fathom'
919
+
920
+ // No need for this anymore:
921
+ // import type { EventOptions } from 'fathom-client'
922
+ ```
923
+
924
+ This simplifies your imports when building custom clients or working with typed event options.
925
+
926
+ ## Troubleshooting
927
+
928
+ ### Common Issues
929
+
930
+ #### Events not appearing in Fathom dashboard
931
+
932
+ **1. Verify your site ID**
933
+
934
+ Your 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`.
935
+
936
+ ```tsx
937
+ // Double-check this value
938
+ <FathomProvider siteId="ABCD1234">
939
+ ```
940
+
941
+ **2. Check for ad blockers**
942
+
943
+ Many ad blockers and privacy extensions block analytics scripts. To test:
944
+ - Open an incognito/private window with extensions disabled
945
+ - Or temporarily whitelist your development domain
946
+
947
+ **3. Domain restrictions**
948
+
949
+ Fathom only tracks events from domains you've configured. For local development:
950
+
951
+ ```tsx
952
+ <FathomProvider
953
+ siteId="YOUR_SITE_ID"
954
+ clientOptions={{
955
+ includedDomains: ['localhost', 'yourdomain.com']
956
+ }}
957
+ >
958
+ ```
959
+
960
+ **4. Inspect network requests**
961
+
962
+ Open your browser's Network tab and look for requests to `cdn.usefathom.com`. If you see:
963
+ - **No requests**: The script isn't loading (check provider setup)
964
+ - **Blocked requests**: Ad blocker is interfering
965
+ - **Failed requests**: Check your site ID and domain configuration
966
+
967
+ #### Duplicate pageview tracking
968
+
969
+ If you're seeing double pageviews, you likely have multiple tracking setups:
970
+
971
+ ```tsx
972
+ // WRONG: Both auto tracking AND manual tracking
973
+ <FathomProvider siteId="YOUR_SITE_ID">
974
+ <NextFathomTrackViewApp /> {/* This tracks pageviews */}
975
+ {/* AND clientOptions.auto defaults to true, which also tracks */}
976
+ </FathomProvider>
977
+
978
+ // CORRECT: Use one or the other
979
+ <FathomProvider siteId="YOUR_SITE_ID" clientOptions={{ auto: false }}>
980
+ <NextFathomTrackViewApp />
981
+ </FathomProvider>
982
+ ```
983
+
984
+ #### useFathom returns undefined methods
985
+
986
+ 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`:
987
+
988
+ 1. Update to the latest version: `npm update react-fathom`
989
+ 2. Ensure your component is inside a `FathomProvider`
990
+
991
+ #### Next.js App Router: "use client" errors
992
+
993
+ Server Components can't use hooks directly. Use the pre-configured client component:
994
+
995
+ ```tsx
996
+ // app/layout.tsx
997
+ import { NextFathomProviderApp } from 'react-fathom/next'
998
+
999
+ export default function RootLayout({ children }) {
1000
+ return (
1001
+ <html>
1002
+ <body>
1003
+ <NextFathomProviderApp siteId="YOUR_SITE_ID">
1004
+ {children}
1005
+ </NextFathomProviderApp>
1006
+ </body>
1007
+ </html>
1008
+ )
1009
+ }
1010
+ ```
1011
+
1012
+ If you need a custom setup, create your own client component wrapper:
1013
+
1014
+ ```tsx
1015
+ // components/AnalyticsProvider.tsx
1016
+ 'use client'
1017
+ import { FathomProvider } from 'react-fathom'
1018
+
1019
+ export function AnalyticsProvider({ children }) {
1020
+ return (
1021
+ <FathomProvider siteId={process.env.NEXT_PUBLIC_FATHOM_SITE_ID}>
1022
+ {children}
1023
+ </FathomProvider>
1024
+ )
1025
+ }
1026
+ ```
1027
+
1028
+ #### Next.js: Environment variables not loading
1029
+
1030
+ Ensure your environment variable is prefixed with `NEXT_PUBLIC_` to be available client-side:
1031
+
1032
+ ```bash
1033
+ # .env.local
1034
+ NEXT_PUBLIC_FATHOM_SITE_ID=YOUR_SITE_ID # ✓ Correct
1035
+ FATHOM_SITE_ID=YOUR_SITE_ID # ✗ Won't work client-side
1036
+ ```
1037
+
1038
+ #### React Native: Events not sending
1039
+
1040
+ **1. Verify react-native-webview is installed**
1041
+
1042
+ The native module requires `react-native-webview`:
1043
+
1044
+ ```bash
1045
+ npm install react-native-webview
1046
+ # For iOS, also run:
1047
+ cd ios && pod install
1048
+ ```
1049
+
1050
+ **2. Check WebView is ready**
1051
+
1052
+ Events are queued until the WebView loads. Use the `onReady` callback to verify:
1053
+
1054
+ ```tsx
1055
+ <NativeFathomProvider
1056
+ siteId="YOUR_SITE_ID"
1057
+ debug={true}
1058
+ onReady={() => console.log('Fathom WebView ready!')}
1059
+ onError={(err) => console.error('Fathom error:', err)}
1060
+ >
1061
+ ```
1062
+
1063
+ **3. Verify network connectivity**
1064
+
1065
+ 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.
1066
+
1067
+ **4. Check for WebView restrictions**
1068
+
1069
+ 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.
1070
+
1071
+ **5. Debug with logging**
1072
+
1073
+ Enable debug mode to see all tracking activity:
1074
+
1075
+ ```tsx
1076
+ <NativeFathomProvider
1077
+ siteId="YOUR_SITE_ID"
1078
+ debug={__DEV__} // Logs all tracking calls
1079
+ >
1080
+ ```
1081
+
1082
+ ### Debugging Tips
1083
+
1084
+ #### Enable verbose logging
1085
+
1086
+ For web, check the browser console. For React Native, enable debug mode:
1087
+
1088
+ ```tsx
1089
+ // React Native
1090
+ <NativeFathomProvider
1091
+ siteId="YOUR_SITE_ID"
1092
+ debug={__DEV__}
1093
+ >
1094
+ ```
1095
+
1096
+ #### Verify tracking in real-time
1097
+
1098
+ Fathom's dashboard updates in real-time. Open your dashboard alongside your app to see events as they're tracked.
1099
+
1100
+ #### Test with a mock client
1101
+
1102
+ For debugging, replace the real client with a mock that logs everything:
1103
+
1104
+ ```tsx
1105
+ const debugClient = {
1106
+ load: (id, opts) => console.log('load:', id, opts),
1107
+ trackPageview: (opts) => console.log('pageview:', opts),
1108
+ trackEvent: (name, opts) => console.log('event:', name, opts),
1109
+ trackGoal: (code, cents) => console.log('goal:', code, cents),
1110
+ setSite: (id) => console.log('setSite:', id),
1111
+ blockTrackingForMe: () => console.log('blocked'),
1112
+ enableTrackingForMe: () => console.log('enabled'),
1113
+ isTrackingEnabled: () => true,
1114
+ }
1115
+
1116
+ <FathomProvider client={debugClient}>
1117
+ ```
1118
+
1119
+ ### Getting Help
1120
+
1121
+ - [Open an issue](https://github.com/ryanhefner/react-fathom/issues) on GitHub
1122
+ - [Search existing issues](https://github.com/ryanhefner/react-fathom/issues?q=is%3Aissue) for solutions
1123
+ - [Fathom Analytics documentation](https://usefathom.com/docs) for platform-specific questions
1124
+
1125
+ ## Contributing
1126
+
1127
+ Contributions are welcome! Whether it's bug fixes, new features, documentation improvements, or examples, we appreciate your help.
1128
+
1129
+ ### Ways to Contribute
1130
+
1131
+ | Type | Description |
1132
+ |------|-------------|
1133
+ | **Bug Reports** | Found a bug? [Open an issue](https://github.com/ryanhefner/react-fathom/issues/new) with reproduction steps |
1134
+ | **Feature Requests** | Have an idea? Discuss it in an issue first |
1135
+ | **Bug Fixes** | PRs for documented issues are always welcome |
1136
+ | **Documentation** | Help improve docs, add examples, fix typos |
1137
+ | **Tests** | Increase test coverage or add edge case tests |
1138
+
1139
+ ### Development Setup
1140
+
1141
+ **Prerequisites:**
1142
+ - Node.js 18+
1143
+ - npm 9+
1144
+
1145
+ **1. Clone and install:**
1146
+
1147
+ ```bash
1148
+ git clone https://github.com/ryanhefner/react-fathom.git
1149
+ cd react-fathom
1150
+ npm install
1151
+ ```
1152
+
1153
+ **2. Run the development workflow:**
1154
+
1155
+ ```bash
1156
+ # Run tests in watch mode during development
1157
+ npm run test:watch
1158
+
1159
+ # Run the full test suite
1160
+ npm test
1161
+
1162
+ # Build the package
1163
+ npm run build
1164
+
1165
+ # Type check without emitting
1166
+ npm run typecheck
1167
+ ```
1168
+
1169
+ ### Project Structure
1170
+
1171
+ ```
1172
+ react-fathom/
1173
+ ├── src/
1174
+ │ ├── index.ts # Main entry point
1175
+ │ ├── FathomProvider.tsx # Core provider component
1176
+ │ ├── FathomContext.tsx # React context
1177
+ │ ├── types.ts # TypeScript definitions
1178
+ │ ├── hooks/ # React hooks
1179
+ │ │ ├── useFathom.ts
1180
+ │ │ ├── useTrackOnClick.ts
1181
+ │ │ ├── useTrackOnMount.ts
1182
+ │ │ └── useTrackOnVisible.ts
1183
+ │ ├── components/ # Declarative tracking components
1184
+ │ │ ├── TrackClick.tsx
1185
+ │ │ ├── TrackPageview.tsx
1186
+ │ │ └── TrackVisible.tsx
1187
+ │ ├── next/ # Next.js-specific exports
1188
+ │ │ └── index.ts
1189
+ │ └── native/ # React Native exports
1190
+ │ ├── index.ts
1191
+ │ ├── FathomWebView.tsx
1192
+ │ ├── createWebViewClient.ts
1193
+ │ ├── NativeFathomProvider.tsx
1194
+ │ ├── useNavigationTracking.ts
1195
+ │ └── useAppStateTracking.ts
1196
+ ├── examples/ # Example applications
1197
+ │ ├── next-app/ # Next.js App Router example
1198
+ │ └── next-pages/ # Next.js Pages Router example
1199
+ └── dist/ # Built output (generated)
1200
+ ```
1201
+
1202
+ ### Testing Guidelines
1203
+
1204
+ We use [Vitest](https://vitest.dev/) for testing. All new features should include tests.
1205
+
1206
+ ```bash
1207
+ # Run all tests
1208
+ npm test
1209
+
1210
+ # Run tests in watch mode
1211
+ npm run test:watch
1212
+
1213
+ # Run tests with coverage
1214
+ npm run test:coverage
1215
+ ```
1216
+
1217
+ **Writing tests:**
1218
+
1219
+ ```tsx
1220
+ // src/hooks/useMyHook.test.tsx
1221
+ import { renderHook } from '@testing-library/react'
1222
+ import { describe, it, expect, vi } from 'vitest'
1223
+ import { useMyHook } from './useMyHook'
1224
+ import { FathomProvider } from '../FathomProvider'
1225
+
1226
+ describe('useMyHook', () => {
1227
+ it('should track events correctly', () => {
1228
+ const mockClient = {
1229
+ trackEvent: vi.fn(),
1230
+ // ... other required methods
1231
+ }
1232
+
1233
+ const wrapper = ({ children }) => (
1234
+ <FathomProvider client={mockClient}>{children}</FathomProvider>
1235
+ )
1236
+
1237
+ const { result } = renderHook(() => useMyHook(), { wrapper })
1238
+
1239
+ result.current.doSomething()
1240
+
1241
+ expect(mockClient.trackEvent).toHaveBeenCalledWith('expected-event', {})
1242
+ })
1243
+ })
1244
+ ```
1245
+
1246
+ ### Code Style
1247
+
1248
+ - **TypeScript**: All code should be fully typed
1249
+ - **Formatting**: We use Prettier (run `npm run format` before committing)
1250
+ - **Linting**: ESLint catches common issues (run `npm run lint`)
1251
+ - **Naming**:
1252
+ - Components: PascalCase (`TrackClick.tsx`)
1253
+ - Hooks: camelCase with `use` prefix (`useFathom.ts`)
1254
+ - Types: PascalCase (`FathomClient`)
1255
+
1256
+ ### Submitting a Pull Request
1257
+
1258
+ 1. **Fork** the repository
1259
+ 2. **Create a branch** from `main`:
1260
+ ```bash
1261
+ git checkout -b fix/my-bug-fix
1262
+ # or
1263
+ git checkout -b feature/my-new-feature
1264
+ ```
1265
+ 3. **Make your changes** with clear, focused commits
1266
+ 4. **Add or update tests** for your changes
1267
+ 5. **Ensure CI passes**:
1268
+ ```bash
1269
+ npm run lint
1270
+ npm test
1271
+ npm run build
1272
+ ```
1273
+ 6. **Submit a PR** with a clear description of what and why
1274
+
1275
+ ### Commit Message Guidelines
1276
+
1277
+ Use clear, descriptive commit messages:
1278
+
1279
+ ```
1280
+ feat: add useTrackOnScroll hook for scroll tracking
1281
+ fix: resolve duplicate pageview tracking in Next.js
1282
+ docs: add troubleshooting section for ad blockers
1283
+ test: add tests for native offline queue
1284
+ refactor: simplify FathomContext default values
1285
+ ```
1286
+
1287
+ Prefixes: `feat`, `fix`, `docs`, `test`, `refactor`, `chore`, `perf`
1288
+
427
1289
  ## License
428
1290
 
429
1291
  [MIT](LICENSE) © [Ryan Hefner](https://www.ryanhefner.com)