Skip to content

feat: add analytics support with use-analytics#90

Open
rsbh wants to merge 3 commits into
mainfrom
feat/analytics
Open

feat: add analytics support with use-analytics#90
rsbh wants to merge 3 commits into
mainfrom
feat/analytics

Conversation

@rsbh
Copy link
Copy Markdown
Member

@rsbh rsbh commented May 19, 2026

Summary

  • Add AnalyticsProvider component using use-analytics + @analytics/google-analytics
  • Client-side only — dynamic imports inside useEffect to avoid SSR crashes
  • Reads analytics.enabled and analytics.googleAnalytics.measurementId from chronicle.yaml
  • Tracks page views on route changes via React Router useLocation
  • Wrapped in try-catch so analytics failures never crash the UI

Config example

analytics:
  enabled: true
  googleAnalytics:
    measurementId: G-XXXXXXXXXX

Test plan

  • Enable analytics in chronicle.yaml with a valid measurement ID
  • Open browser Network tab — verify google-analytics.com requests fire
  • Navigate between pages — verify page view events sent
  • Disable analytics (enabled: false) — verify no GA requests
  • Remove analytics config entirely — verify no errors

🤖 Generated with Claude Code

Client-side only analytics via use-analytics. Dynamically imports
GA plugin inside useEffect to avoid SSR issues. Wraps app in
AnalyticsProvider that reads config from chronicle.yaml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chronicle Ready Ready Preview, Comment May 20, 2026 6:15am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Review Change Stack

Warning

Rate limit exceeded

@rsbh has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 23 minutes and 35 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 16c2b71f-6c97-427b-97ed-ca34b471def2

📥 Commits

Reviewing files that changed from the base of the PR and between c471043 and a978811.

📒 Files selected for processing (2)
  • packages/chronicle/src/components/analytics/AnalyticsProvider.tsx
  • packages/chronicle/src/server/App.tsx
📝 Walkthrough

Walkthrough

This PR integrates analytics tracking into Chronicle by adding the analytics and use-analytics libraries, creating an AnalyticsProvider component that initializes analytics with optional Google Analytics support, and wrapping the app root to enable automatic page view tracking on route changes.

Changes

Analytics Integration

Layer / File(s) Summary
Analytics dependencies
packages/chronicle/package.json
Adds analytics (^0.8.19), use-analytics (^1.1.0), and @analytics/google-analytics to enable analytics initialization and page tracking.
AnalyticsProvider component
packages/chronicle/src/components/analytics/AnalyticsProvider.tsx
New component that initializes analytics instance with optional Google Analytics plugin, provides analytics context via use-analytics, and includes internal PageViewTracker that calls page() whenever React Router pathname changes.
App root integration
packages/chronicle/src/server/App.tsx
Wraps root app rendering with AnalyticsProvider, passing config.analytics configuration to enable or disable analytics tracking.

Sequence Diagram

sequenceDiagram
  participant App
  participant AnalyticsProvider
  participant useAnalytics
  participant PageViewTracker
  participant AnalyticsCore as analytics instance
  
  App->>AnalyticsProvider: Pass config and children
  AnalyticsProvider->>AnalyticsCore: Initialize with plugins (conditionally load Google Analytics)
  AnalyticsProvider->>useAnalytics: Provide instance
  AnalyticsProvider->>PageViewTracker: Render with instance
  
  Note over PageViewTracker: Route pathname changes
  PageViewTracker->>AnalyticsCore: Call page() with new pathname
  AnalyticsCore-->>PageViewTracker: Track page view
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • raystack/chronicle#22: Adds the analytics field to ChronicleConfig, which this PR then consumes in AnalyticsProvider initialization.

Suggested reviewers

  • rohilsurana
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add analytics support with use-analytics' clearly and concisely describes the main change: adding analytics functionality using the use-analytics library.
Description check ✅ Passed The description is well-related to the changeset, providing clear context about the AnalyticsProvider component, configuration, implementation details, and a comprehensive test plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/analytics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/chronicle/src/components/analytics/AnalyticsProvider.tsx`:
- Around line 47-53: The root wrapper switches between a Fragment and a Provider
which causes remounts; in AnalyticsProvider always render the Provider wrapper
(use Provider instance={analytics} even if analytics is null) so the outer
element type never changes, and move any analytics-dependent children (e.g.,
PageViewTracker) to render conditionally inside the Provider when analytics is
non-null; update the component that returns Provider/children accordingly
(references: AnalyticsProvider, Provider, PageViewTracker, analytics, children).
- Around line 28-45: The current useEffect with dynamic import chains doesn't
catch async failures; change it to an inner async function (e.g., async function
initAnalytics()) that awaits the imports (await
import('`@analytics/google-analytics`') and await import('analytics')) inside a
try/catch so all Promise rejections are caught, build the plugins array after
the awaited googleAnalytics import, and call setAnalytics(Analytics({ app:
'chronicle', plugins })) only after checking a cancellation flag; introduce a
cancelled boolean (let cancelled = false) and return a cleanup () => { cancelled
= true } to avoid state updates after unmount and skip setAnalytics when
cancelled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2e3dab0f-1c3f-4beb-bfa9-595b266062fb

📥 Commits

Reviewing files that changed from the base of the PR and between 0679bdf and c471043.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • packages/chronicle/package.json
  • packages/chronicle/src/components/analytics/AnalyticsProvider.tsx
  • packages/chronicle/src/server/App.tsx

Comment thread packages/chronicle/src/components/analytics/AnalyticsProvider.tsx Outdated
Comment on lines +47 to +53
if (!analytics) return <>{children}</>

return (
<Provider instance={analytics}>
<PageViewTracker />
{children}
</Provider>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

cat -n packages/chronicle/src/components/analytics/AnalyticsProvider.tsx

Repository: raystack/chronicle

Length of output: 1932


Avoid changing the root wrapper structure on mount.

Lines 49–56 conditionally return either a Fragment or a Provider component. When analytics initializes as null and then updates after the async load completes, React treats these as different root element types, causing child components to remount. This resets internal state and retriggeres effects/fetches.

Consider always rendering the Provider wrapper, or defer initial render until analytics is available.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/chronicle/src/components/analytics/AnalyticsProvider.tsx` around
lines 47 - 53, The root wrapper switches between a Fragment and a Provider which
causes remounts; in AnalyticsProvider always render the Provider wrapper (use
Provider instance={analytics} even if analytics is null) so the outer element
type never changes, and move any analytics-dependent children (e.g.,
PageViewTracker) to render conditionally inside the Provider when analytics is
non-null; update the component that returns Provider/children accordingly
(references: AnalyticsProvider, Provider, PageViewTracker, analytics, children).

try/catch didn't catch .then() chain failures. Refactored to
async/await so all import errors are caught, and added cancellation
flag to prevent state updates after unmount.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@rohilsurana rohilsurana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #90 — Add analytics support with use-analytics

Overall: Solid approach. Dynamic imports inside useEffect correctly avoid SSR issues, and the cancellation flag prevents state updates on unmounted components. A few things to tighten up:

Issues

  1. AnalyticsProvider wraps the SSR render tree but use-analytics is not SSR-safe — The <Provider instance={analytics}> from use-analytics is only rendered when analytics is non-null (client-side), which is correct. However, AnalyticsProvider itself is imported in App.tsx which runs during SSR. The use-analytics and analytics packages are not in ssr.external in vite-config.ts. If Vite tries to bundle them for SSR, it may pull in browser-only code from the analytics plugins. Verify this works with a production build (chronicle build && chronicle start), not just dev mode. If it fails, add analytics and use-analytics to ssr.external.

  2. config.analytics ?? {} passes an empty object as AnalyticsConfig — In App.tsx line 41, when analytics is undefined, you pass {}. But AnalyticsConfig expects { enabled?: boolean; googleAnalytics?: ... }. An empty object works at runtime since all fields are optional, but it would be cleaner to default enabled to false explicitly: config.analytics ?? { enabled: false }. This makes the disabled-by-default behavior explicit rather than relying on !config.enabled being truthy for undefined.

  3. Dependencies added to root package.json AND packages/chronicle/package.json — The lockfile diff shows @analytics/google-analytics, analytics, and use-analytics added to the root workspace package.json as well as the chronicle package. They should only be in packages/chronicle/package.json. The root additions are redundant and will cause them to be hoisted differently.

  4. No way to pass custom analytics plugins — The current design only supports Google Analytics. The use-analytics library supports many plugins. Consider making the architecture extensible (e.g., analytics.plugins array in config) so users don't need to fork for Plausible, Mixpanel, etc. Not blocking for this PR, but worth a follow-up issue.

Minor

  1. PageViewTracker fires on every pathname change but not on initial load — The useEffect with [pathname, page] will fire on mount (initial page view) and subsequent navigations, which is correct. But since SSR pre-renders the page, the first page() call fires after hydration, which might double-count if GA's script tag also fires a pageview on load. Verify there's no duplicate initial pageview in the Network tab.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants