Thisbefine
Examples

Next.js App Router

The full setup for Next.js (yes, App Router)

Next.js App Router Example

A complete, copy-paste-ready setup for Next.js App Router. No guessing required.

Project Structure

Here's what you'll end up with:

layout.tsx
page.tsx
login/page.tsx
layout.tsx
dashboard/page.tsx
settings/page.tsx
.env.local

Setup

Install the SDK

pnpm add @thisbefine/analytics

That's the only dependency.

Add to Root Layout

app/layout.tsx
import { Analytics, BugReportFAB } from '@thisbefine/analytics/next';
import './globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <Analytics
          apiKey={process.env.NEXT_PUBLIC_THISBEFINE_API_KEY!}
          debug={process.env.NODE_ENV === 'development'}
          config={{
            errors: {
              enabled: true,
              captureNetworkErrors: process.env.NODE_ENV === 'production',
              maxBreadcrumbs: 50,
            },
          }}
        />
        <BugReportFAB />
      </body>
    </html>
  );
}

Done. Every page now tracks pageviews. Every error gets captured. Users can report bugs.

Zero config option: Set NEXT_PUBLIC_TBF_API_KEY in your .env.local and you can use <Analytics /> with no props at all.

Authentication Flow

The important bits: identify on login, reset on logout.

Login Page

app/(auth)/login/page.tsx
'use client';

import { useIdentify, useTrack } from '@thisbefine/analytics/next';
import { useState } from 'react';
import { useRouter } from 'next/navigation';

export default function LoginPage() {
  const identify = useIdentify();
  const trackLogin = useTrack('login_completed');
  const trackLoginFailed = useTrack('login_failed');
  const router = useRouter();
  const [error, setError] = useState<string | null>(null);

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);

    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        body: JSON.stringify({
          email: formData.get('email'),
          password: formData.get('password'),
        }),
      });

      if (!response.ok) {
        trackLoginFailed({ reason: 'invalid_credentials' });
        setError('Invalid credentials');
        return;
      }

      const { user } = await response.json();

      // Step 1: Tell us who they are
      identify(user.id, {
        email: user.email,
        name: user.name,
        plan: user.plan,
      });

      // Step 2: Track the login
      trackLogin({ method: 'email' });

      // Step 3: Send them on their way
      router.push('/dashboard');
    } catch (err) {
      trackLoginFailed({ reason: 'network_error' });
      setError('Something went wrong');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" placeholder="Email" required />
      <input name="password" type="password" placeholder="Password" required />
      {error && <p className="text-red-500">{error}</p>}
      <button type="submit">Login</button>
    </form>
  );
}

Logout Handler

components/logout-button.tsx
'use client';

import { useReset, useTrack } from '@thisbefine/analytics/next';
import { useRouter } from 'next/navigation';

export const LogoutButton = () => {
  const reset = useReset();
  const trackLogout = useTrack('logout');
  const router = useRouter();

  const handleLogout = async () => {
    // Track BEFORE reset (otherwise we lose the userId)
    trackLogout();

    // Call your logout API
    await fetch('/api/auth/logout', { method: 'POST' });

    // Clear analytics identity
    reset();

    router.push('/login');
  };

  return <button onClick={handleLogout}>Logout</button>;
};

Feature Tracking

Track Button Clicks

components/cta-button.tsx
'use client';

import { useTrack } from '@thisbefine/analytics/next';

interface CTAButtonProps {
  variant: 'primary' | 'secondary';
  location: string;
  children: React.ReactNode;
}

export const CTAButton = ({ variant, location, children }: CTAButtonProps) => {
  const trackClick = useTrack('cta_clicked');

  return (
    <button
      onClick={() => trackClick({ variant, location })}
      className={variant === 'primary' ? 'bg-blue-500' : 'bg-gray-500'}
    >
      {children}
    </button>
  );
};

Track Feature Usage

app/(dashboard)/dashboard/page.tsx
'use client';

import { useTrack } from '@thisbefine/analytics/next';

export default function DashboardPage() {
  const trackExport = useTrack('data_exported');

  const handleExport = async (format: 'csv' | 'json') => {
    trackExport({ format });
    // Your export logic...
  };

  return (
    <div>
      <h1>Dashboard</h1>
      <button onClick={() => handleExport('csv')}>Export CSV</button>
      <button onClick={() => handleExport('json')}>Export JSON</button>
    </div>
  );
}

Workspace/Team Context

For B2B apps with workspaces or teams:

hooks/use-workspace-analytics.ts
'use client';

import { useGroup } from '@thisbefine/analytics/next';
import { useEffect } from 'react';

interface Workspace {
  id: string;
  name: string;
  plan: string;
  memberCount: number;
}

export const useWorkspaceAnalytics = (workspace: Workspace | null) => {
  const group = useGroup();

  useEffect(() => {
    if (workspace) {
      group(workspace.id, {
        name: workspace.name,
        plan: workspace.plan,
        employeeCount: workspace.memberCount,
      });
    }
  }, [workspace, group]);
};

Error Boundary

Catch React errors and send them to Thisbefine:

components/error-boundary.tsx
'use client';

import { Component, ReactNode } from 'react';
import { getAnalytics } from '@thisbefine/analytics';

interface Props {
  children: ReactNode;
}

interface State {
  hasError: boolean;
  error: Error | null;
}

export class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false, error: null };

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // Send to Thisbefine
    getAnalytics()?.captureException(error, {
      componentStack: errorInfo.componentStack,
    });
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="p-8 text-center">
          <h2>Something went wrong</h2>
          <p className="text-muted-foreground">{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Try again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

Environment Variables

.env.local
NEXT_PUBLIC_THISBEFINE_API_KEY=tif_your_api_key_here

Never commit .env.local. Make sure it's in your .gitignore.

That's it. You now have analytics, error tracking, and bug reports. Time to ship.

On this page