Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a59f1ca
Add Nextra documentation site and slim down README
claude Jan 22, 2026
654eaa6
Convert docs site from Nextra to Chakra UI
claude Jan 22, 2026
5b5030a
Add Nextra feature parity: syntax highlighting, search, auto-sidebar
claude Jan 22, 2026
d670acb
Add breadcrumbs, last updated, and package manager tabs
claude Jan 22, 2026
87a9bab
Add tsbuildinfo to gitignore
claude Jan 22, 2026
6d5cb6c
Add Accordion and FileTree components
claude Jan 22, 2026
19078ed
Add polish features: OG images, sitemap, print styles, changelog
claude Jan 22, 2026
8fd6fdc
Add announcement banner, 404 page, and keyboard shortcuts
claude Jan 22, 2026
eb13b4d
Add landing page and move documentation under /docs
claude Jan 22, 2026
d0a17b1
Integrate react-fathom into docs site
claude Jan 22, 2026
4dfbb61
Add React and React Native examples with self-documenting docs
claude Jan 22, 2026
9446097
Add docs and events pages to Next.js examples
claude Jan 22, 2026
62e11ed
Fix docs site build issues for Chakra UI v3 compatibility
claude Jan 22, 2026
f644fb5
Add .gitignore for docs site build artifacts
claude Jan 22, 2026
b59a2cc
Configure static export for docs and Next.js app example
claude Jan 22, 2026
4a7070e
Update Next.js examples to use published package version
claude Jan 22, 2026
53f751b
Add React Router integration for pageview tracking
claude Jan 22, 2026
855930b
Add React Router documentation and update example
claude Jan 22, 2026
cced3c3
Add Gatsby integration for pageview tracking
claude Jan 22, 2026
2bb14e0
Add TanStack Router integration for pageview tracking
claude Jan 22, 2026
10ad99c
Add deep merge for nested FathomProvider default options
claude Jan 22, 2026
7e6b32d
Add debug mode to FathomProvider for development and demos
claude Jan 22, 2026
f6ca3f4
Add EventStream component and event tracking for docs site
claude Jan 22, 2026
71e5b2b
Add debug mode documentation to README
claude Jan 22, 2026
2422d2a
Remove unnecessary optional chaining on tracking methods
claude Jan 22, 2026
e0a6981
Add keyboard shortcut to toggle EventStream panel
claude Jan 22, 2026
f3e469a
Upgrade dependencies
ryanhefner Jan 22, 2026
3263d3e
Link docs and examples to local react-fathom package
claude Jan 22, 2026
4a74142
Bump docs dependencies to latest versions
ryanhefner Jan 22, 2026
cc41dd3
Fix search result URLs by stripping .html extension
claude Jan 22, 2026
7235e78
Update landing page with all supported frameworks
claude Jan 22, 2026
21cb905
Add react-fathom/debug subpackage with EventStream component
claude Jan 22, 2026
e910a9d
Add error handling, transformUrl, and EventStream tests
claude Jan 22, 2026
c136426
Extract URL building logic into shared utility
claude Jan 23, 2026
2cf79bd
Add tests for onError, transformUrl, and buildTrackingUrl
claude Jan 23, 2026
563dbe0
Add framework logo grid to landing page
claude Jan 23, 2026
2a60cf8
Add SEO optimization with Open Graph tags and images
claude Jan 23, 2026
ad5ddd8
Add shared example-ui package for consistent styling across examples
claude Jan 23, 2026
8e6b7a2
Add EventStreamPanel to shared UI package for consistent styling
claude Jan 23, 2026
e30fb58
Fix EventStream visibility on docs site
claude Jan 23, 2026
eee929c
Add debug logging for debug mode state
claude Jan 23, 2026
f38bcaa
Add global event dispatch for cross-context debug events
claude Jan 23, 2026
8bb1201
Add TanStack Router and Gatsby example sites
claude Jan 23, 2026
c0f96fd
Remove NODE_ENV check from debug mode logging
claude Jan 23, 2026
117d183
Add Vercel deployment configs for all sites
claude Jan 23, 2026
21ef5d2
Add debug logging for FathomProvider mounting diagnostics
claude Jan 23, 2026
fa1c4b3
Add webpack alias to resolve react-fathom to single instance
claude Jan 23, 2026
60ce8af
Add Turbopack config for Next.js 16 compatibility
claude Jan 23, 2026
9117a97
Fix Turbopack config with relative paths and root setting
claude Jan 24, 2026
19efa41
Fix React context sharing for linked package in docs site
claude Jan 24, 2026
58a44f5
Add dev scripts to run all sites simultaneously
claude Jan 24, 2026
8e299c0
Update example sites with minimal design aesthetic
claude Jan 24, 2026
cf546d8
Add comprehensive codebase review report
claude Feb 8, 2026
d91a4ad
Implement codebase review recommendations
claude Feb 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ node_modules
coverage
dist
types
*.tsbuildinfo

# Include Rollup config
!rollup.config.js
Expand Down
188 changes: 188 additions & 0 deletions CODEBASE_REVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Codebase Review: react-fathom

**Date:** 2026-02-08
**Version:** 0.2.0

## Project Overview

**react-fathom** is a React wrapper library for [Fathom Analytics](https://usefathom.com/), a privacy-first analytics service. It provides:

- Automatic pageview tracking via declarative components and hooks
- Integrations for 6 frameworks: React, Next.js (App + Pages Router), React Router, Gatsby, TanStack Router, and React Native
- Full TypeScript support with strict typing
- Tree-shakeable builds (UMD, ESM, CJS)
- Debug mode with event stream UI
- React Native support via WebView with offline queuing

The codebase is well-organized across ~64 source files (~40 implementation + ~24 test files, ~8,300 lines total), with a Rollup build pipeline, Vitest test suite, CircleCI CI/CD, and a full Next.js documentation site.

---

## Architecture & Design (Strong)

**Strengths:**
- Clean separation of concerns: core provider, framework-specific adapters, hooks, and declarative components are each in their own modules
- Framework-agnostic core (`FathomProvider` + `FathomContext`) with per-framework adapters that compose on top
- Both declarative (`<TrackClick>`, `<TrackPageview>`, `<TrackVisible>`) and imperative (`useFathom()`) APIs
- Proper React patterns: Context, `useCallback`/`useMemo`, ref forwarding, `useImperativeHandle`
- `clientRef` prop allows parent access to the client instance, solving a common context limitation
- Nested provider support with option inheritance (shallow merge)

**Observations:**
- Multiple framework adapters each implement their own `buildUrl` function with near-identical logic (Next.js utils, React Router, Gatsby, TanStack Router). This is a clear opportunity to extract a shared URL-building utility.

---

## TypeScript & Type Safety (Strong)

The project uses `"strict": true` throughout with well-defined types exported for consumers.

**Minor issues:**

| Location | Issue |
|---|---|
| `src/native/useNavigationTracking.ts:60,82` | Uses `as any` type casts when traversing nested navigator state |
| `src/native/FathomWebView.tsx:182-186` | `error.message` in a catch block could be `undefined` if the thrown value isn't an `Error` |
| `src/types.ts:140` | `debug?: DebugOptions \| boolean` — the boolean shorthand mapping to `{ enabled: true, console: true }` could surprise users expecting `console: false` |

---

## Error Handling (Adequate, with gaps)

**What's covered:**
- `onError` callback on `FathomProvider` for catching tracking failures
- `safeClientCall` wrapper prevents unhandled exceptions in tracking methods
- Try-catch around debug subscriber notifications (`FathomProvider.tsx:118`)
- localStorage errors handled gracefully in `EventStream.tsx:114-116`
- React Native WebView message parsing wrapped in try-catch

**Gaps identified:**

1. **No `siteId` validation** — an empty string or `undefined` is silently accepted
2. **`FathomWebView.tsx:182-186`** — injected JS error handler accesses `error.message` without verifying the caught value is an `Error` instance
3. **`NativeFathomProvider.tsx:87-91`** — `client.setWebViewReady()` is not wrapped in try-catch
4. **`createWebViewClient.ts:95-98`** — when the command queue overflows `maxQueueSize`, the oldest command is silently dropped with only a debug log. No error callback is invoked.
5. **No rate limiting** on tracking calls — a malicious or buggy component could fire events in a tight loop
6. **No timeout** for WebView readiness in React Native — if the WebView never loads, queued commands sit indefinitely

---

## Security Concerns (Low Risk)

1. **`transformUrl` callbacks not validated** — In all router adapters (e.g., `ReactRouterFathomTrackView.tsx:111-117`), the return value of user-provided `transformUrl` is used directly without any validation. A misconfigured transform could inject unexpected values.

2. **Debug mode global event broadcasting** — `FathomProvider.tsx:87` dispatches `CustomEvent('react-fathom:debug', ...)` on `window`, which is globally accessible. If debug mode is accidentally enabled in production, tracking data leaks to any listener.

3. **WebView message origin** — `FathomWebView.tsx:212` processes incoming WebView messages without verifying origin.

4. **No input sanitization** on event names or goal parameters before sending to Fathom.

These are all low-severity given this is a client-side analytics library, but worth documenting.

---

## Potential Bugs

### Medium Priority

1. **Shared module-level debug event counter** (`FathomProvider.tsx:12-13`):
```typescript
let debugEventCounter = 0
const generateDebugEventId = () => `debug-${Date.now()}-${++debugEventCounter}`
```
This counter is module-scoped, meaning multiple `FathomProvider` instances share it. Debug event IDs can collide across providers. Should be instance-scoped (e.g., via `useRef`).

2. **Unconditional console.log on mount** (`FathomProvider.tsx:57-60`):
```typescript
useEffect(() => {
console.log('[react-fathom] FathomProvider mounted, debugProp:', debugProp, ...)
}, [])
```
This logs on every provider mount regardless of whether debug mode is enabled.

3. **Race condition in `NativeFathomProvider`** — Multiple rapid `setWebViewReady()` calls could cause duplicate event processing from the queue.

### Low Priority

4. **`useTrackOnMount` suppresses exhaustive-deps** (line 23) — Intentionally fires only on mount, but if `options` changes dynamically, the stale closure won't pick it up.

5. **`EventStream` localStorage contention** (lines 131-139) — Multiple tabs writing simultaneously with no locking; last write wins.

6. **`useTrackOnVisible` `hasTracked` ref never resets** — If a component transitions from `trackOnce=false` to `trackOnce=true` after already tracking, it won't re-track.

---

## Test Coverage (Good)

- **24 test files** covering all major functionality
- `FathomProvider.test.tsx` alone is ~750 lines with comprehensive scenarios
- All hooks, components, and framework adapters have dedicated test files
- Uses Vitest + @testing-library/react + jsdom

**Gaps:**
- Error recovery paths are lightly tested (only ~4 tests for `onError` in `FathomProvider.test.tsx`)
- No tests for localStorage failures in `EventStream`
- No tests for malformed WebView messages
- No tests for queue overflow behavior in `createWebViewClient`

---

## Code Quality & Consistency (Strong)

**Positives:**
- Consistent style enforced by ESLint + Prettier (single quotes, no semicolons)
- Display names set on all components for React DevTools
- Good JSDoc comments with `@example` blocks
- Proper cleanup in all `useEffect` hooks (event listeners, observers)
- `useCallback`/`useMemo` used appropriately to prevent unnecessary re-renders

**Issues:**
- **Duplicate URL-building logic** across 4+ files (`next/utils.ts`, `ReactRouterFathomTrackView.tsx`, `GatsbyFathomTrackView.tsx`, `TanStackRouterFathomTrackView.tsx`) — should be extracted to a shared utility
- **Inconsistent error logging** — some files use `console.error`, others `console.warn`, some use both
- **`disableAutoTrack` naming** — negative boolean props (`disable*`) are harder to reason about than positive ones (`enableAutoTrack`)

---

## Build & Infrastructure (Solid)

- **Rollup** config handles 7 entry points across 3 output formats (UMD, ESM, CJS)
- **`add-use-client.js`** post-build script properly adds `'use client'` directives for Next.js RSC compatibility
- **CircleCI** runs build + tests with coverage reporting to Coveralls and Codecov
- **Tree-shaking** works correctly via package.json `exports` map with separate entry points per framework

---

## Performance (Good)

- IntersectionObserver used for visibility tracking with reasonable threshold (0.1)
- Queue management in React Native prevents unbounded memory growth
- Event listeners properly cleaned up

**Minor concerns:**
- `EventStream` accesses localStorage on every visibility toggle — could benefit from debouncing
- URL-building functions in router adapters are not memoized (reconstructed on every navigation)

---

## Summary

| Category | Rating | Notes |
|---|---|---|
| Architecture | Strong | Clean modular design with good separation |
| TypeScript | Strong | Strict mode, well-typed APIs, minor `any` casts |
| Error Handling | Adequate | Core paths covered; gaps in edge cases |
| Security | Low risk | Appropriate for client-side analytics lib |
| Testing | Good | 24 test files; some edge case gaps |
| Code Quality | Strong | Consistent style, proper React patterns |
| Build/Infra | Solid | Multi-format builds, CI/CD, coverage |

---

## Top Recommended Improvements

1. **Extract shared URL-building utility** to eliminate duplication across 4+ router adapters
2. **Make debug event counter instance-scoped** (use `useRef` instead of module-level variable)
3. **Remove unconditional `console.log` on provider mount** (or gate it behind debug mode check)
4. **Add `siteId` validation** (warn on empty/missing values)
5. **Add tests for edge cases**: error recovery, queue overflow, malformed WebView messages
6. **Add production warning** if debug mode is accidentally enabled
Loading