Quick Setup
Install the SDK
Copy
npm install @userboost/sdk
Environment Variables
Add your API keys to your environment files:.env.production
Copy
NEXT_PUBLIC_USERBOOST_API_KEY=ub_live_your_production_key_here
Initialize in _app.js
Copy
// pages/_app.js
import { useEffect } from "react";
import { UserBoost } from "@userboost/sdk";
function MyApp({ Component, pageProps }) {
useEffect(() => {
UserBoost.init({
apiKey: process.env.NEXT_PUBLIC_USERBOOST_API_KEY,
debug: process.env.NODE_ENV === "development",
});
}, []);
return <Component {...pageProps} />;
}
export default MyApp;
Page Tracking
Page-Specific Tracking
Copy
// pages/dashboard.js
import { useEffect } from "react";
import { UserBoost } from "@userboost/sdk";
import { useUser } from "../hooks/useUser";
export default function Dashboard() {
const { user } = useUser();
useEffect(() => {
if (user?.id) {
UserBoost.event("dashboard_viewed", {
user: { id: user.id },
properties: {
user_plan: user.plan,
last_login: user.lastLogin,
},
});
}
}, [user]);
return (
<div>
<h1>Dashboard</h1>
{/* Your dashboard content */}
</div>
);
}
API Routes Integration
Track Server-Side Events
Copy
// pages/api/auth/signup.js
import { UserBoost } from "@userboost/sdk";
// Initialize UserBoost for server-side usage
UserBoost.init({
apiKey: process.env.USERBOOST_API_KEY, // Note: No NEXT_PUBLIC_ prefix
endpoint: "https://api.userboo.st/v1",
});
export default async function handler(req, res) {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}
try {
const { email, password, name, source } = req.body;
// Create user in your database
const user = await createUser({ email, password, name });
// Track signup on server-side
UserBoost.identify({
id: user.id,
email: user.email,
name: user.name,
signup_date: user.createdAt,
signup_method: "email",
source: source || "direct",
});
UserBoost.event("user_signed_up", {
user: { id: user.id },
properties: {
source: source || "direct",
signup_method: "email",
},
});
res.status(200).json({ success: true, user });
} catch (error) {
// Track signup errors
UserBoost.event("signup_error", {
user: { id: req.body.email }, // Temp ID
properties: {
error_message: error.message,
signup_method: "email",
},
});
res.status(500).json({ error: error.message });
}
}
Track Subscription Events
Copy
// pages/api/webhooks/stripe.js
import { UserBoost } from "@userboost/sdk";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
UserBoost.init({
apiKey: process.env.USERBOOST_API_KEY,
});
export default async function handler(req, res) {
const sig = req.headers["stripe-signature"];
try {
const event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
if (event.type === "customer.subscription.created") {
const subscription = event.data.object;
const customer = await stripe.customers.retrieve(subscription.customer);
// Track successful subscription
UserBoost.event("subscription_created", {
user: { id: customer.metadata.user_id }, // Store user ID in Stripe metadata
properties: {
plan: subscription.items.data[0].price.nickname,
amount: subscription.items.data[0].price.unit_amount / 100,
currency: subscription.currency,
billing_cycle: subscription.items.data[0].price.recurring.interval,
trial_days: subscription.trial_end
? Math.ceil(
(subscription.trial_end - subscription.current_period_start) /
86400
)
: 0,
},
});
}
res.json({ received: true });
} catch (error) {
console.error("Webhook error:", error);
res.status(400).send(`Webhook Error: ${error.message}`);
}
}
Component Examples
Landing Page CTA Tracking
Copy
// components/HeroCTA.jsx
import { UserBoost } from "@userboost/sdk";
import Link from "next/link";
function HeroCTA() {
const handleCTAClick = () => {
UserBoost.event("cta_clicked", {
user: { id: "user_123" },
properties: {
cta_location: "hero_section",
cta_text: "Start Free Trial",
page: "homepage",
timestamp: new Date().toISOString(),
},
});
};
return (
<div className="hero-section">
<h1>Reduce User Churn with Smart Email Automation</h1>
<p>Track user behavior and send timely nudges when they get stuck.</p>
<Link href="/signup">
<button className="cta-button" onClick={handleCTAClick}>
Start Free Trial
</button>
</Link>
</div>
);
}
export default HeroCTA;
Pricing Page Tracking
Copy
// pages/pricing.js
import { useState } from "react";
import { UserBoost } from "@userboost/sdk";
export default function Pricing() {
const [billingCycle, setBillingCycle] = useState("monthly");
const handlePlanSelect = (planName, price) => {
UserBoost.event("pricing_plan_selected", {
user: { id: "user_123" },
properties: {
plan: planName,
price: price,
billing_cycle: billingCycle,
page: "pricing",
currency: "USD",
},
});
// Redirect to signup with plan
window.location.href = `/signup?plan=${planName}&billing=${billingCycle}`;
};
const handleBillingToggle = (cycle) => {
setBillingCycle(cycle);
UserBoost.event("billing_cycle_changed", {
user: { id: "user_123" },
properties: {
from_cycle: billingCycle,
to_cycle: cycle,
page: "pricing",
},
});
};
return (
<div className="pricing-page">
<h1>Choose Your Plan</h1>
{/* Billing toggle */}
<div className="billing-toggle">
<button
className={billingCycle === "monthly" ? "active" : ""}
onClick={() => handleBillingToggle("monthly")}
>
Monthly
</button>
<button
className={billingCycle === "annual" ? "active" : ""}
onClick={() => handleBillingToggle("annual")}
>
Annual (Save 20%)
</button>
</div>
{/* Pricing cards */}
<div className="pricing-grid">
<div className="pricing-card">
<h3>Starter</h3>
<p className="price">
${billingCycle === "monthly" ? "29" : "24"}/month
</p>
<button
onClick={() =>
handlePlanSelect("starter", billingCycle === "monthly" ? 29 : 24)
}
>
Choose Starter
</button>
</div>
<div className="pricing-card featured">
<h3>Pro</h3>
<p className="price">
${billingCycle === "monthly" ? "79" : "63"}/month
</p>
<button
onClick={() =>
handlePlanSelect("pro", billingCycle === "monthly" ? 79 : 63)
}
>
Choose Pro
</button>
</div>
</div>
</div>
);
}
Advanced Patterns
Custom Hook for Next.js
Copy
// hooks/useUserBoostTracking.js
import { useCallback } from "react";
import { useRouter } from "next/router";
import { UserBoost } from "@userboost/sdk";
import { useUser } from "./useUser";
export function useUserBoostTracking() {
const { user } = useUser();
const router = useRouter();
const track = useCallback(
(eventName, properties = {}) => {
const userId = user?.id || `anonymous_${Date.now()}`;
UserBoost.event(eventName, {
user: { id: userId },
properties: {
page: router.pathname,
...properties,
// Add Next.js specific context
is_authenticated: !!user,
user_plan: user?.plan,
route: router.route,
query_params:
Object.keys(router.query).length > 0 ? router.query : undefined,
},
});
},
[user, router]
);
const identify = useCallback(
(userData) => {
UserBoost.identify({
id: userData.id,
email: userData.email,
name: userData.name,
...userData,
// Add Next.js specific user data
last_page: router.pathname,
signup_page: userData.signupPage || "unknown",
});
},
[router]
);
const trackPagePerformance = useCallback(() => {
// Track Next.js page load performance
if (typeof window !== "undefined" && window.performance) {
const navigation = window.performance.getEntriesByType("navigation")[0];
track("page_performance", {
load_time: navigation.loadEventEnd - navigation.fetchStart,
dom_ready_time:
navigation.domContentLoadedEventEnd - navigation.fetchStart,
first_paint:
window.performance.getEntriesByName("first-paint")[0]?.startTime,
largest_contentful_paint: window.performance.getEntriesByName(
"largest-contentful-paint"
)[0]?.startTime,
});
}
}, [track]);
return { track, identify, trackPagePerformance };
}
Server-Side Props Tracking
Copy
// pages/blog/[slug].js
import { useEffect } from "react";
import { UserBoost } from "@userboost/sdk";
import { useUser } from "../../hooks/useUser";
export default function BlogPost({ post, readTime, viewCount }) {
const { user } = useUser();
useEffect(() => {
const userId = user?.id || `anonymous_${Date.now()}`;
UserBoost.event("blog_post_viewed", {
user: { id: userId },
properties: {
post_slug: post.slug,
post_title: post.title,
post_category: post.category,
estimated_read_time: readTime,
total_view_count: viewCount,
is_authenticated: !!user,
},
});
}, [post, user, readTime, viewCount]);
return (
<article>
<h1>{post.title}</h1>
<div className="post-content">{post.content}</div>
</article>
);
}
export async function getStaticProps({ params }) {
const post = await getBlogPost(params.slug);
const readTime = calculateReadTime(post.content);
const viewCount = await getViewCount(params.slug);
return {
props: {
post,
readTime,
viewCount,
},
revalidate: 3600, // Revalidate every hour
};
}
export async function getStaticPaths() {
const posts = await getAllBlogPosts();
return {
paths: posts.map((post) => ({
params: { slug: post.slug },
})),
fallback: "blocking",
};
}
Middleware Integration
Track API Route Usage
Copy
// middleware.js
import { NextResponse } from "next/server";
import { UserBoost } from "@userboost/sdk";
// Initialize UserBoost for middleware
UserBoost.init({
apiKey: process.env.USERBOOST_API_KEY,
});
export function middleware(request) {
// Track API route usage
if (request.nextUrl.pathname.startsWith("/api/")) {
const userId = request.headers.get("x-user-id") || "anonymous";
UserBoost.event("api_request", {
user: { id: userId },
properties: {
endpoint: request.nextUrl.pathname,
method: request.method,
user_agent: request.headers.get("user-agent"),
referer: request.headers.get("referer"),
country: request.geo?.country,
city: request.geo?.city,
},
});
}
return NextResponse.next();
}
export const config = {
matcher: "/api/:path*",
};
TypeScript Configuration
Typed Events for Next.js
Copy
// types/tracking.ts
export interface UserBoostEventData {
user: {
id: string;
};
properties: Record<string, any>;
}
export interface NextJSEventData extends UserBoostEventData {
properties: {
page: string;
route?: string;
is_authenticated?: boolean;
user_plan?: string;
query_params?: Record<string, string | string[]>;
[key: string]: any;
};
}
export interface BlogEventData extends UserBoostEventData {
properties: {
post_slug: string;
post_title: string;
post_category: string;
estimated_read_time: number;
max_scroll_depth?: number;
time_on_page?: number;
is_authenticated?: boolean;
[key: string]: any;
};
}
export interface APIEventData extends UserBoostEventData {
properties: {
endpoint: string;
method: string;
response_time?: number;
status_code?: number;
error_message?: string;
[key: string]: any;
};
}
Copy
// hooks/useTypedTracking.ts
import { UserBoost } from "@userboost/sdk";
import {
NextJSEventData,
BlogEventData,
APIEventData,
} from "../types/tracking";
export function useTypedTracking() {
const trackPageView = (data: NextJSEventData) => {
UserBoost.event("page_view", data);
};
const trackBlogView = (data: BlogEventData) => {
UserBoost.event("blog_post_viewed", data);
};
const trackAPICall = (data: APIEventData) => {
UserBoost.event("api_request", data);
};
return {
trackPageView,
trackBlogView,
trackAPICall,
};
}
Testing Your Integration
Development Test Panel
Copy
// components/DevTestPanel.jsx
import { useState } from "react";
import { UserBoost } from "@userboost/sdk";
export default function DevTestPanel() {
const [isVisible, setIsVisible] = useState(false);
const [testResults, setTestResults] = useState([]);
const runTests = async () => {
const tests = [
{
name: "Page View Tracking",
test: () =>
UserBoost.event("test_page_view", {
user: { id: "test_user_123" },
properties: {
page: "/test",
timestamp: new Date().toISOString(),
},
}),
},
{
name: "User Identification",
test: () =>
UserBoost.identify({
id: "test_user_123",
email: "test@example.com",
name: "Test User",
}),
},
{
name: "Custom Event",
test: () =>
UserBoost.event("test_custom_event", {
user: { id: "test_user_123" },
properties: {
custom_property: "test_value",
timestamp: new Date().toISOString(),
},
}),
},
];
UserBoost.debug(true);
for (const test of tests) {
try {
await test.test();
setTestResults((prev) => [
...prev,
{ name: test.name, status: "passed" },
]);
} catch (error) {
setTestResults((prev) => [
...prev,
{ name: test.name, status: "failed", error: error.message },
]);
}
}
};
if (process.env.NODE_ENV !== "development") {
return null;
}
return (
<div
style={{
position: "fixed",
bottom: 20,
right: 20,
width: 300,
background: "#f8f9fa",
border: "1px solid #dee2e6",
borderRadius: 8,
padding: 16,
fontSize: 14,
zIndex: 9999,
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 10,
}}
>
<strong>UserBoost Test Panel</strong>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? "Hide" : "Show"}
</button>
</div>
{isVisible && (
<div>
<button
onClick={runTests}
style={{ marginBottom: 10, padding: "8px 12px", cursor: "pointer" }}
>
Run Tests
</button>
<div>
{testResults.map((result, i) => (
<div
key={i}
style={{
color: result.status === "passed" ? "green" : "red",
marginBottom: 4,
}}
>
{result.name}: {result.status}
{result.error && (
<div style={{ fontSize: 12 }}>{result.error}</div>
)}
</div>
))}
</div>
<div style={{ marginTop: 10, fontSize: 12, color: "#666" }}>
Check browser console for detailed logs
</div>
</div>
)}
</div>
);
}
Copy
function MyApp({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
{process.env.NODE_ENV === "development" && <DevTestPanel />}
</>
);
}
Best Practices for Next.js
1. Client vs Server Configuration
Use different API keys for client-side (withNEXT_PUBLIC_
) and server-side tracking.
2. Route-Based Tracking
Leverage Next.js router events for automatic page view tracking.3. API Route Integration
Track server-side events in API routes for complete user journey visibility.4. Performance Monitoring
Use Next.js performance APIs to track page load times and Core Web Vitals.5. Middleware Usage
Use Next.js middleware for tracking API usage and geographic data.6. Static Generation
Pass tracking data throughgetStaticProps
and getServerSideProps
for better context.
Ready to track your Next.js app? Install the SDK and start with the basic setup above. Your events will appear in the UserBoost dashboard within seconds!