A lightweight, privacy-focused React component for integrating Umami Analytics into your website. Umami is an open-source, privacy-friendly alternative to Google Analytics that helps you understand your website's traffic without compromising user privacy.
- 🚀 Easy integration with props or environment variables
- 🔒 Privacy-focused analytics
- ⚡ Lazy loading support
- 🐛 Debug mode for development
- 📦 Zero runtime dependencies
- 🔍 Full TypeScript support
- 🎯 Custom event tracking hook
- 📊 Manual pageview tracking with UTM support
- 🔄 Async UTM parameter fetching
- 🌐 Custom domain support
- ⚙️ Configurable script attributes
- ⚛️ React 18 & 19 Support - Compatible with both React 18.2+ and React 19
- React: 18.2+ or 19.0+
- Node.js: 16.0+
The package is available on both NPM and GitHub Packages:
pnpm add @danielgtmn/umami-react
# First, configure npm to use GitHub Packages for @danielgtmn scope
echo "@danielgtmn:registry=https://npm.pkg.github.com" >> .npmrc
# Then install
pnpm add @danielgtmn/umami-react
import UmamiAnalytics from '@danielgtmn/umami-react';
function App() {
return (
<div>
<UmamiAnalytics
url="https://analytics.example.com"
websiteId="your-website-id"
/>
{/* Your app content */}
</div>
);
}
Create a .env
file:
UMAMI_URL=https://analytics.example.com
UMAMI_ID=your-website-id
UMAMI_DEBUG=false
UMAMI_LAZY_LOAD=true
Then use the component:
import UmamiAnalytics from '@danielgtmn/umami-react';
function App() {
return (
<div>
<UmamiAnalytics />
{/* Your app content */}
</div>
);
}
Prop | Type | Description | Default |
---|---|---|---|
url |
string |
Your Umami instance URL | UMAMI_URL env var |
websiteId |
string |
Your website tracking ID | UMAMI_ID env var |
debug |
boolean |
Enable debug logging | false |
lazyLoad |
boolean |
Enable lazy loading | false |
onlyInProduction |
boolean |
Only load in production | true |
domains |
string[] |
Custom domains for tracking | undefined |
scriptAttributes |
Record<string, string> |
Additional script attributes | undefined |
Variable | Type | Description | Default |
---|---|---|---|
UMAMI_URL |
string |
Your Umami instance URL | "" |
UMAMI_ID |
string |
Your website tracking ID | "" |
UMAMI_DEBUG |
string |
Enable debug logging | "false" |
UMAMI_LAZY_LOAD |
string |
Enable lazy loading | "false" |
Enable lazy loading to improve initial page load performance:
<UmamiAnalytics
url="https://analytics.example.com"
websiteId="your-website-id"
lazyLoad={true}
/>
Enable debug mode for development:
<UmamiAnalytics
url="https://analytics.example.com"
websiteId="your-website-id"
debug={true}
/>
Track only specific domains:
<UmamiAnalytics
url="https://analytics.example.com"
websiteId="your-website-id"
domains={['example.com', 'app.example.com']}
/>
Add custom attributes to the script tag:
<UmamiAnalytics
url="https://analytics.example.com"
websiteId="your-website-id"
scriptAttributes={{
'data-cache': 'true',
'data-host-url': 'https://custom-host.com'
}}
/>
Use the useUmami
hook for custom event tracking:
import { useUmami } from '@danielgtmn/umami-react';
function MyComponent() {
const { track } = useUmami();
const handleClick = () => {
track('button-click', { button: 'header-cta' });
};
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
The useUmami
hook provides comprehensive pageview tracking capabilities, perfect for scenarios where you need to disable auto-tracking and manually control pageview events with custom UTM parameters.
Note: This library correctly integrates with Umami's official tracking API. Pageviews are tracked using Umami's standard
umami.track()
method without custom event names.
import { useUmami } from '@danielgtmn/umami-react';
function MyComponent() {
const { trackPageview } = useUmami();
const handlePageview = () => {
trackPageview({
url: '/custom-page',
title: 'Custom Page Title',
referrer: document.referrer,
});
};
return (
<button onClick={handlePageview}>
Track Pageview
</button>
);
}
import { useUmami } from '@danielgtmn/umami-react';
function MyComponent() {
const { trackPageviewWithUTM } = useUmami();
const handleUTMPageview = () => {
trackPageviewWithUTM({
utm_source: 'newsletter',
utm_medium: 'email',
utm_campaign: 'spring-sale',
utm_term: 'discount',
utm_content: 'header-link',
});
};
return (
<button onClick={handleUTMPageview}>
Track UTM Pageview
</button>
);
}
For scenarios where you need to fetch UTM parameters from your backend using a unique ID:
import { useUmami, UTMFetcher } from '@danielgtmn/umami-react';
function MyComponent() {
const { trackPageviewAsync } = useUmami();
// Define your UTM fetcher function
const fetchUTMData: UTMFetcher = async (utmId: string) => {
const response = await fetch(`/api/utm/${utmId}`);
const data = await response.json();
return {
utm_source: data.source,
utm_medium: data.medium,
utm_campaign: data.campaign,
utm_term: data.term,
utm_content: data.content,
};
};
const handleAsyncPageview = async () => {
// This will fetch UTM data from your backend and track the pageview
await trackPageviewAsync('unique-utm-id-123', fetchUTMData, {
// Optional additional data
custom_property: 'custom_value',
});
};
return (
<button onClick={handleAsyncPageview}>
Track Async UTM Pageview
</button>
);
}
The useUmami
hook provides the following methods:
const {
track, // Original event tracking
trackPageview, // Manual pageview tracking
trackPageviewWithUTM, // Pageview with UTM parameters
trackPageviewAsync // Async UTM fetching + pageview
} = useUmami();
import { PageviewData, UTMFetcher } from '@danielgtmn/umami-react';
// PageviewData interface
const pageviewData: PageviewData = {
url: '/custom-page',
title: 'Page Title',
referrer: 'https://example.com',
utm_source: 'google',
utm_medium: 'cpc',
utm_campaign: 'summer-sale',
utm_term: 'shoes',
utm_content: 'ad-1',
utm_id: 'unique-id-123',
// Any additional custom properties
custom_field: 'custom_value',
};
// UTMFetcher function type
const myUTMFetcher: UTMFetcher = async (utmId: string) => {
// Your implementation
return {
utm_source: 'backend-source',
utm_medium: 'backend-medium',
// ... other UTM parameters
};
};
When using manual pageview tracking, you might want to disable Umami's automatic pageview tracking by adding the data-auto-track="false"
attribute:
<UmamiAnalytics
url="https://analytics.example.com"
websiteId="your-website-id"
scriptAttributes={{
'data-auto-track': 'false'
}}
/>
The async UTM fetching includes built-in error handling. If the UTM fetcher fails, it will:
- Log an error to the console (in debug mode)
- Fall back to basic pageview tracking with the provided
utm_id
- Include any additional data you provided
// This will gracefully handle network errors
await trackPageviewAsync('utm-id', failingFetcher, {
fallback_source: 'direct',
});
The package includes full TypeScript support with exported interfaces:
import UmamiAnalytics, { UmamiAnalyticsProps, UmamiConfig } from '@danielgtmn/umami-react';
const config: UmamiAnalyticsProps = {
url: 'https://analytics.example.com',
websiteId: 'your-website-id',
debug: true,
lazyLoad: true,
};
<UmamiAnalytics {...config} />
// pages/_app.tsx or app/layout.tsx
import UmamiAnalytics from '@danielgtmn/umami-react';
export default function App({ Component, pageProps }) {
return (
<>
<UmamiAnalytics
url="https://analytics.example.com"
websiteId="your-website-id"
/>
<Component {...pageProps} />
</>
);
}
// src/App.tsx
import UmamiAnalytics from '@danielgtmn/umami-react';
function App() {
return (
<div className="App">
<UmamiAnalytics
url="https://analytics.example.com"
websiteId="your-website-id"
/>
{/* Your app content */}
</div>
);
}
# Install dependencies
pnpm install
# Start development mode
pnpm dev
# Run linting
pnpm lint
# Auto-fix linting issues
pnpm lint:fix
# Format code
pnpm format
# Type check
pnpm type-check
# Run tests
pnpm test
# Run tests once
pnpm test:run
# Run tests with coverage
pnpm test:coverage
# Run tests in watch mode
pnpm test:watch
# Check bundle size
pnpm size
# Analyze bundle
pnpm analyze
# Build for production
pnpm build
# Clean build artifacts
pnpm clean
This project uses modern development tools to ensure code quality:
Git hooks automatically run quality checks before commits:
- Pre-commit: TypeScript check, linting, and tests
- Commit-msg: Validates commit message format
Commits follow Conventional Commits format:
type(scope): description
# Examples:
feat: add new tracking feature
fix: resolve lazy loading bug
docs: update README
test: add component tests
Allowed types: build
, chore
, ci
, docs
, feat
, fix
, perf
, refactor
, revert
, style
, test
This project uses Vitest for testing with React Testing Library.
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
# Run tests in watch mode
pnpm test:watch
This project uses GitHub Actions for continuous integration and deployment:
-
CI Pipeline (
.github/workflows/ci.yml
): Runs on every push/PR to main- Tests across multiple Node.js versions (18.x, 20.x, 22.x)
- Linting and type checking
- Bundle size monitoring
- Test coverage reporting
-
Release Pipeline (
.github/workflows/publish.yml
): Runs on releases- Runs full test suite before publishing
- Publishes to NPM with public access
The project includes bundle size monitoring and analysis:
# Check current bundle sizes
pnpm size
# Analyze bundle composition
pnpm analyze
Bundle size limits:
- ESM: ≤ 3 KB (gzipped)
- CJS: ≤ 3.5 KB (gzipped)
Contributions are welcome! Please ensure:
- Follow commit conventions: Use Conventional Commits format
- Quality checks: Git hooks automatically run:
- TypeScript type checking
- ESLint linting
- Test execution
- Commit message validation
- Manual verification:
- All tests pass:
pnpm test:run
- Code is linted:
pnpm lint
- Bundle size is within limits:
pnpm size
- TypeScript types are correct:
pnpm type-check
- All tests pass:
# Install dependencies
pnpm install
# Git hooks are automatically set up via prepare script
# If needed manually: pnpm prepare
Please feel free to submit a Pull Request!