Google Analytics 4 (GA4) is powerful — but let's be honest, the default gtag snippets get messy fast. I tried multiple react ga4 libraries to implement on my ecommerce website and all provides the wrapper only. That's why I built a library @connectaryal/google-analytics — a type-safe, developer-friendly GA4 wrapper for React and Next.js.
No more guesswork, no more typos. Just clean analytics tracking.
What You Get
- Easy initialization with a singleton instance
- Type-safe events (no more "pageveiw" mistakes)
- Ready-to-use methods for interaction, auth, and e-commerce
- Debug mode for development
- Works in both React & Next.js
Step 1: Create Your Google Analytics 4 Property
Before writing any code, set up your GA4 property:
- Visit Google Analytics.
- Click Admin (bottom left) > Create Property.
- Name your property, timezone/currency, and click Next.
- Add a new Web data stream for your site.
- Copy your Measurement ID (looks like
G-XXXXXXXXXX
).
Step 2: Install the npm Package
Skip the manual gtag script and use a robust, type-safe library:
npm install @connectaryal/google-analytics
# or
yarn add @connectaryal/google-analytics
# or
pnpm add @connectaryal/google-analytics
See the package on npm: @connectaryal/google-analytics
Step 3: Initialize Analytics
The library uses a context provider for global React integration. Wrap your app at the highest level:
import { GAProvider } from "@connectaryal/google-analytics";
function App() {
return (
<GAProvider config={{ measurementId: "G-XXXXXXXXXX" }}>
<YourApp />
</GAProvider>
);
}
Pro Tip: For Next.js, wrap your custom App
or RootLayout
component.
Step 4: Track Events: Page Interaction, Engagement, E-commerce
Track a Page View
import { useGoogleAnalytics } from '@connectaryal/google-analytics';
function HomePage() {
const { trackPage } = useGoogleAnalytics();
useEffect(() => {
trackPage(); // auto detects current page
}, [trackPage]);
}
Custom Engagement & Conversion
import {
EventCategory,
useGoogleAnalytics,
} from '@connectaryal/google-analytics';
function VideoPlayer() {
const { trackCustomEvent } = useGoogleAnalytics();
const handleVideoPlay = () => {
trackCustomEvent({
name: "video_play",
category: EventCategory.ENGAGEMENT,
params: {
video_title: "Intro Demo",
video_duration: 120
},
});
};
return (
<video onPlay={handleVideoPlay}>
{/* video content */}
</video>
);
}
E-commerce Tracking
import { useGAEcommerce } from "@connectaryal/google-analytics";
function ProductPage() {
const { trackCart, trackPurchase } = useGAEcommerce();
const handleAddToCart = () => {
trackCart({
action: "add_to_cart",
items: [{
item_id: "SKU123",
item_name: "Wireless Headphones",
price: 99.99,
quantity: 1
}],
value: 99.99
});
};
const handlePurchase = () => {
trackPurchase({
transactionId: "ORDER123",
value: 199.98,
items: [{
item_id: "SKU123",
item_name: "Wireless Headphones",
price: 99.99,
quantity: 2
}]
});
};
return (
<div>
<button onClick={handleAddToCart}>Add to Cart</button>
<button onClick={handlePurchase}>Buy Now</button>
</div>
);
}
Step 5: Advanced Tracking: Validated, Typed & Error-Proof
With the GATracker
class (used internally, or for advanced users):
- Every method checks for empty arrays, missing values, or invalid input.
- All events are type-checked with TypeScript.
- Debug mode logs warnings and errors clearly.
Example: Handling Validation
const tracker = new GATracker({
measurementId: "G-XXXXXXXXXX",
currency: "USD",
debug: true
});
try {
await tracker.trackCart({
action: "add_to_cart",
items: [], // Empty array!
value: 99.99
});
} catch (e) {
// Will throw: "GA4: Cannot track add_to_cart with empty items"
console.error(e);
}
Step 6: Debug Mode
Turn on debugging to see event logs:
<GAProvider config={{
measurementId: "G-XXXXXXXXXX",
debug: process.env.NODE_ENV === "development"
}}>
<App />
</GAProvider>
Now you'll see every event in the console during development.
Real-World Examples
Complete E-commerce Flow
function ShoppingCart() {
const {
trackCart,
trackBeginCheckout,
trackShippingInfo,
trackPaymentInfo,
trackPurchase
} = useGAEcommerce();
const [cartItems, setCartItems] = useState([]);
const [total, setTotal] = useState(0);
const handleAddToCart = (product) => {
const newItem = {
item_id: product.sku,
item_name: product.name,
price: product.price,
quantity: 1
};
setCartItems([...cartItems, newItem]);
setTotal(total + product.price);
trackCart({
action: "add_to_cart",
items: [newItem],
value: product.price
});
};
const handleCheckout = () => {
trackBeginCheckout({
value: total,
items: cartItems
});
};
const handleShipping = (shippingTier) => {
trackShippingInfo({
items: cartItems,
value: total,
shipping_tier: shippingTier
});
};
const handlePayment = (paymentMethod) => {
trackPaymentInfo(
cartItems,
total,
paymentMethod
);
};
const completePurchase = (orderId) => {
trackPurchase({
transactionId: orderId,
value: total,
items: cartItems
});
};
return (
<div>
{/* Cart UI implementation */}
<button onClick={handleCheckout}>Proceed to Checkout</button>
</div>
);
}
User Authentication Tracking
function AuthForm() {
const { trackLogin, trackSignUp, trackException } = useGoogleAnalytics();
const handleGoogleLogin = async () => {
try {
const user = await signInWithGoogle();
trackLogin({
method: "google",
customParams: {
user_type: user.isFirstTime ? "new" : "returning"
}
});
} catch (error) {
trackException({
description: "Google login failed",
fatal: false
});
}
};
const handleEmailSignup = async (email, password) => {
try {
await createUserWithEmail(email, password);
trackSignUp({
method: "email",
customParams: {
signup_source: "landing_page"
}
});
} catch (error) {
trackException({
description: "Email signup failed",
fatal: false
});
}
};
return (
<div>
<button onClick={handleGoogleLogin}>Login with Google</button>
<form onSubmit={(e) => {
e.preventDefault();
handleEmailSignup(email, password);
}}>
{/* form fields */}
<button type="submit">Sign Up</button>
</form>
</div>
);
}
Search & Content Engagement
function SearchPage() {
const { trackSearch, trackShare, trackEngagement } = useGoogleAnalytics();
const [searchTerm, setSearchTerm] = useState("");
const [results, setResults] = useState([]);
const handleSearch = async () => {
const searchResults = await searchProducts(searchTerm);
setResults(searchResults);
trackSearch({
searchTerm,
options: {
search_results: searchResults.length,
search_category: "products"
}
});
};
const handleShare = (article) => {
trackShare({
contentType: "article",
contentId: article.id,
method: "twitter",
customParams: {
article_category: article.category
}
});
};
const handleScroll = (scrollDepth) => {
if (scrollDepth > 75) {
trackEngagement({
type: "scroll",
options: {
scroll_depth: scrollDepth,
page_type: "search_results"
}
});
}
};
return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
/>
{results.map(result => (
<div key={result.id}>
<h3>{result.title}</h3>
<button onClick={() => handleShare(result)}>Share</button>
</div>
))}
</div>
);
}
Why Type Safety Matters
- No typos: "pageveiw" won't even compile
- Strict params: keeps you aligned with GA schema
-
Reusable methods: no more raw
gtag("event", …)
everywhere - Validation: automatic parameter validation prevents silent failures
- IntelliSense: full autocomplete for all GA4 events and parameters
Full API Reference
Here's everything included in the package:
Core Analytics Methods
Method | Description |
---|---|
trackPage |
Page views with automatic path detection |
trackCustomEvent |
Custom events with validation |
trackSearch |
Site search tracking |
trackEngagement |
User engagement (scroll, click, etc.) |
trackShare |
Content sharing events |
trackVideoPlay |
Video interaction tracking |
trackFormSubmission |
Form submission events |
trackTiming |
Performance timing events |
trackLogin |
User authentication |
trackSignUp |
User registration |
trackException |
Error and exception tracking |
E-commerce Methods
Method | Description |
---|---|
trackItem |
Item selection/view/list events |
trackCart |
Cart actions (add/remove/view/update) |
trackWishlist |
Wishlist operations |
trackBeginCheckout |
Checkout initiation |
trackShippingInfo |
Shipping information during checkout |
trackPaymentInfo |
Payment method selection |
trackPurchase |
Purchase completion |
trackRefund |
Purchase refunds |
trackPromotion |
Promotion view/select events |
Advanced Features
- Automatic Validation: All methods validate parameters before sending
- Error Handling: Graceful error handling with detailed error messages
- Debug Mode: Development-friendly logging and warnings
- Performance Optimized: Event batching and memory leak prevention
- TypeScript First: Full type safety with intelligent autocomplete
- Framework Agnostic: Works with React, Next.js, and other React frameworks
Next.js Integration
App Router (Next.js 13+)
// app/layout.tsx
import { GAProvider } from "@connectaryal/google-analytics";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<GAProvider
config={{
measurementId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID!,
debug: process.env.NODE_ENV === "development"
}}
>
{children}
</GAProvider>
</body>
</html>
);
}
// app/components/PageTracker.tsx
"use client";
import { usePathname } from "next/navigation";
import { useGoogleAnalytics } from "@connectaryal/google-analytics";
import { useEffect } from "react";
export function PageTracker() {
const pathname = usePathname();
const { trackPage } = useGoogleAnalytics();
useEffect(() => {
trackPage({ path: pathname });
}, [pathname, trackPage]);
return null;
}
Pages Router (Next.js 12 and below)
// pages/_app.tsx
import type { AppProps } from "next/app";
import { GAProvider, useGoogleAnalytics } from "@connectaryal/google-analytics";
import { useRouter } from "next/router";
import { useEffect } from "react";
function PageTracker() {
const router = useRouter();
const { trackPage } = useGoogleAnalytics();
useEffect(() => {
const handleRouteChange = (url: string) => {
trackPage({ path: url });
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events, trackPage]);
return null;
}
export default function App({ Component, pageProps }: AppProps) {
return (
<GAProvider
config={{
measurementId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID!,
debug: process.env.NODE_ENV === "development"
}}
>
<PageTracker />
<Component {...pageProps} />
</GAProvider>
);
}
Advanced Configuration
Environment Variables
# .env.local (Next.js)
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
# .env (Create React App)
REACT_APP_GA_MEASUREMENT_ID=G-XXXXXXXXXX
GDPR/Privacy Compliance
<GAProvider
config={{
measurementId: "G-XXXXXXXXXX",
debug: process.env.NODE_ENV === "development",
currency: "USD",
customConfig: {
// GDPR compliance settings
analytics_storage: "granted",
ad_storage: "denied",
ad_user_data: "denied",
ad_personalization: "denied"
}
}}
>
<App />
</GAProvider>
Complete E-commerce Implementation
Product Catalog
function ProductGrid({ products }) {
const { trackItem } = useGAEcommerce();
const handleProductView = (product) => {
trackItem({
action: "view_item",
items: [{
item_id: product.sku,
item_name: product.name,
item_category: product.category,
item_brand: product.brand,
price: product.price,
quantity: 1
}],
value: product.price,
customParams: {
product_position: product.position,
list_name: "product_catalog"
}
});
};
return (
<div className="product-grid">
{products.map((product, index) => (
<ProductCard
key={product.id}
product={product}
onView={() => handleProductView(product)}
/>
))}
</div>
);
}
Shopping Cart Component
function ShoppingCartPage() {
const {
trackCart,
trackBeginCheckout
} = useGAEcommerce();
const { trackCustomEvent } = useGoogleAnalytics();
const [cart, setCart] = useState([]);
const updateQuantity = (itemId, newQuantity) => {
const updatedCart = cart.map(item =>
item.item_id === itemId
? { ...item, quantity: newQuantity }
: item
);
setCart(updatedCart);
const updatedItem = updatedCart.find(item => item.item_id === itemId);
trackCart({
action: "update_cart",
items: [updatedItem],
value: updatedItem.price * updatedItem.quantity
});
};
const removeFromCart = (itemId) => {
const itemToRemove = cart.find(item => item.item_id === itemId);
setCart(cart.filter(item => item.item_id !== itemId));
trackCart({
action: "remove_from_cart",
items: [itemToRemove],
value: itemToRemove.price * itemToRemove.quantity
});
};
const proceedToCheckout = () => {
const total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
trackBeginCheckout({
value: total,
items: cart,
customParams: {
checkout_step: 1,
cart_size: cart.length
}
});
};
return (
<div>
{cart.map(item => (
<div key={item.item_id}>
<span>{item.item_name}</span>
<input
type="number"
value={item.quantity}
onChange={(e) => updateQuantity(item.item_id, parseInt(e.target.value))}
/>
<button onClick={() => removeFromCart(item.item_id)}>Remove</button>
</div>
))}
<button onClick={proceedToCheckout}>Checkout</button>
</div>
);
}
Complete Checkout Flow
function CheckoutProcess() {
const {
trackShippingInfo,
trackPaymentInfo,
trackPurchase
} = useGAEcommerce();
const [checkoutData, setCheckoutData] = useState({
items: [],
total: 0,
shipping: null,
payment: null
});
const handleShippingSelection = (shippingOption) => {
setCheckoutData(prev => ({ ...prev, shipping: shippingOption }));
trackShippingInfo({
items: checkoutData.items,
value: checkoutData.total,
shipping_tier: shippingOption.type,
customParams: {
shipping_cost: shippingOption.cost
}
});
};
const handlePaymentSelection = (paymentMethod) => {
setCheckoutData(prev => ({ ...prev, payment: paymentMethod }));
trackPaymentInfo(
checkoutData.items,
checkoutData.total + (checkoutData.shipping?.cost || 0),
paymentMethod.type,
{
payment_provider: paymentMethod.provider
}
);
};
const completePurchase = async () => {
try {
const order = await processPayment(checkoutData);
trackPurchase({
transactionId: order.id,
value: order.total,
items: order.items,
customParams: {
affiliation: "Online Store",
shipping: checkoutData.shipping.cost,
payment_type: checkoutData.payment.type
}
});
// Redirect to success page
} catch (error) {
trackException({
description: "Purchase failed",
fatal: false,
customParams: {
error_code: error.code
}
});
}
};
return (
<div>
<ShippingOptions onSelect={handleShippingSelection} />
<PaymentMethods onSelect={handlePaymentSelection} />
<button onClick={completePurchase}>Complete Order</button>
</div>
);
}
Performance & Best Practices
Event Batching
The library automatically batches events to improve performance:
// These events will be batched together
const { trackCustomEvent } = useGoogleAnalytics();
trackCustomEvent({ name: "button_click", params: { button: "header_cta" } });
trackCustomEvent({ name: "section_view", params: { section: "hero" } });
trackCustomEvent({ name: "scroll_depth", params: { depth: 50 } });
Error Handling
function RobustTracking() {
const { trackCustomEvent, trackException } = useGoogleAnalytics();
const trackWithErrorHandling = async (eventName, params) => {
try {
await trackCustomEvent({ name: eventName, params });
} catch (error) {
// Log to your error monitoring service
console.error("Analytics tracking failed:", error);
// Optionally track the tracking failure
trackException({
description: `Analytics tracking failed: ${eventName}`,
fatal: false
});
}
};
return (
<button onClick={() => trackWithErrorHandling("cta_click", { location: "hero" })}>
Track Safely
</button>
);
}
Testing Your Implementation
Debug Mode Output
When debug mode is enabled, you'll see console output like:
GA4: Initializing with measurement ID: G-XXXXXXXXXX
GA4: Tracking event "page_view" with params: { page_title: "Home", page_location: "/" }
GA4: Event sent successfully ✅
GA4: Tracking event "add_to_cart" with params: { value: 99.99, items: [...] }
GA4: Event sent successfully ✅
Verifying Events in GA4
- Go to your GA4 property
- Navigate to Reports > Realtime
- Trigger events on your site
- See events appear in real-time (may take 1-2 minutes)
For detailed event debugging, use Configure > DebugView in GA4.
Comparison with Other Libraries
Feature | @connectaryal/google-analytics | react-ga4 | gtag directly |
---|---|---|---|
TypeScript Support | ✅ Full | ⚠️ Basic | ❌ None |
E-commerce Events | ✅ Complete | ⚠️ Limited | ⚠️ Manual |
Error Handling | ✅ Built-in | ❌ Manual | ❌ Manual |
Event Validation | ✅ Automatic | ❌ None | ❌ None |
React Hooks | ✅ Modern | ✅ Basic | ❌ None |
Debug Mode | ✅ Comprehensive | ⚠️ Basic | ❌ None |
Documentation | ✅ Complete | ⚠️ Basic | ❌ Google docs |
Why Use Type-Safe Analytics?
- No more silent failures: Type errors and validation mean you catch mistakes early
- Enterprise-grade: Handles all GA4 e-commerce, engagement, and conversion flows
- Debugging is simple: Errors and warnings are clear, actionable, and logged only in dev mode
- Future-proof: Built with modern React patterns and best practices
- Maintainable: Clean, readable code that your team can easily understand and extend
Final Thoughts
Modern analytics isn't just about tracking everything—it's about tracking the right things, with clean, validated code you can trust. By starting with a GA4 property and using a type-safe, React-optimized library, your data is actionable and your integration is future-proof.
Ready to track smarter?
Links:
- NPM Package: @connectaryal/google-analytics
- GitHub Repository: connectaryal/google-analytics
- Documentation: GitHub Wiki
- Issues & Support: GitHub Issues
Found an issue or want to contribute? Open an issue on GitHub.
Top comments (0)