Skip to main content

Quick Setup

Install the SDK

npm install @userboost/sdk

Environment Variables

Add your API keys to your environment files:
.env.production
NEXT_PUBLIC_USERBOOST_API_KEY=ub_live_your_production_key_here

Initialize in _app.js

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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;
  };
}
// 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

// 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>
  );
}
Add to your pages/_app.js:
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 (with NEXT_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 through getStaticProps 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!
I