add-analytics

Wire PostHog analytics, Sentry error tracking, and a health endpoint into any web project. Reads your stack, installs the right packages, and instruments your app so you stop flying blind in production.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "add-analytics" with this command: npx skills add tushaarmehtaa/tushar-skills/tushaarmehtaa-tushar-skills-add-analytics

Wire observability into any web project. This skill adds event tracking, error monitoring, and a status endpoint — the three things every indie builder says they'll add later but never does.

Phase 1: Detect the Stack

Read the project and figure out what to install.

1.1 Framework

  • next.config.* → Next.js (check App Router vs Pages Router)
  • vite.config.* → Vite / React SPA
  • astro.config.* → Astro
  • nuxt.config.* → Nuxt
  • Python backend (main.py, app.py, manage.py) → FastAPI / Django / Flask

1.2 Existing Analytics

Check if any analytics/monitoring is already installed:

  • posthog-js or posthog-node → PostHog already present
  • @sentry/nextjs or @sentry/react or sentry-sdk → Sentry already present
  • @vercel/analytics → Vercel Analytics present
  • @google-analytics or gtag → GA present

If already installed, audit the setup instead of installing fresh. Check for gaps (missing error boundaries, no server-side tracking, no health endpoint).

1.3 Ask the User

Detected: [framework]
Existing analytics: [what's already there, or "none"]

I'll set up:
1. PostHog — event tracking, user identification, feature flags
2. Sentry — error tracking with source maps
3. Health endpoint — /api/status for uptime monitoring

Need your project keys:
- PostHog API key (get from app.posthog.com → Project Settings)
- Sentry DSN (get from sentry.io → Project Settings → Client Keys)

Or I can set up the code and you add the keys later.

Phase 2: PostHog Analytics

2.1 Install

Next.js:

npm install posthog-js posthog-node

React SPA (Vite):

npm install posthog-js

Python:

pip install posthog

2.2 Client-Side Provider

Next.js App Router — create app/providers.tsx:

'use client';

import posthog from 'posthog-js';
import { PostHogProvider as PHProvider, usePostHog } from 'posthog-js/react';
import { useEffect } from 'react';

if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
  posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
    api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
    person_profiles: 'identified_only',
    capture_pageview: false, // We handle this manually for SPA navigation
    capture_pageleave: true,
    loaded: (posthog) => {
      if (process.env.NODE_ENV === 'development') {
        // Disable in dev unless you want to test
        // posthog.opt_out_capturing();
      }
    },
  });
}

export function PostHogProvider({ children }: { children: React.ReactNode }) {
  return <PHProvider client={posthog}>{children}</PHProvider>;
}

Wrap the app in layout.tsx:

import { PostHogProvider } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <PostHogProvider>{children}</PostHogProvider>
      </body>
    </html>
  );
}

React SPA (Vite) — create lib/analytics.ts:

import posthog from 'posthog-js';

let initialized = false;

export function initAnalytics() {
  if (initialized || typeof window === 'undefined') return;

  const key = import.meta.env.VITE_POSTHOG_KEY;
  if (!key) return;

  posthog.init(key, {
    api_host: import.meta.env.VITE_POSTHOG_HOST || 'https://us.i.posthog.com',
    person_profiles: 'identified_only',
    capture_pageview: true,
  });

  initialized = true;
}

export { posthog };

Call initAnalytics() in your app entry point.

2.3 Page View Tracking (Next.js SPA)

PostHog doesn't auto-track SPA navigation. Add a component that fires on route changes:

// components/PostHogPageView.tsx
'use client';

import { usePathname, useSearchParams } from 'next/navigation';
import { usePostHog } from 'posthog-js/react';
import { useEffect, Suspense } from 'react';

function PageViewTracker() {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const posthog = usePostHog();

  useEffect(() => {
    if (pathname && posthog) {
      let url = window.origin + pathname;
      const search = searchParams.toString();
      if (search) url += '?' + search;
      posthog.capture('$pageview', { $current_url: url });
    }
  }, [pathname, searchParams, posthog]);

  return null;
}

export function PostHogPageView() {
  return (
    <Suspense fallback={null}>
      <PageViewTracker />
    </Suspense>
  );
}

Add to layout:

<PostHogProvider>
  <PostHogPageView />
  {children}
</PostHogProvider>

2.4 User Identification

When a user logs in, identify them in PostHog so events are linked to a person:

import posthog from 'posthog-js';

// Call this after successful auth sync
function identifyUser(user: { id: string; email?: string; name?: string }) {
  posthog.identify(user.id, {
    email: user.email,
    name: user.name,
  });
}

// Call this on logout
function resetUser() {
  posthog.reset();
}

Wire this into your auth hook or auth sync callback.

2.5 Custom Event Tracking

Create a thin wrapper for type safety and consistency:

// lib/track.ts
import posthog from 'posthog-js';

type TrackEvent =
  | { event: 'signed_up'; properties?: { method: string } }
  | { event: 'created_project'; properties: { projectId: string } }
  | { event: 'upgraded_plan'; properties: { plan: string; price: number } }
  | { event: 'used_feature'; properties: { feature: string } };

export function track({ event, properties }: TrackEvent) {
  posthog.capture(event, properties);
}

Usage:

track({ event: 'created_project', properties: { projectId: '123' } });

The union type ensures you can't track events with wrong properties. Add your events to the union as your product grows.

2.6 Server-Side Tracking (Next.js)

For events that happen on the server (API routes, webhooks):

// lib/posthog-server.ts
import { PostHog } from 'posthog-node';

let client: PostHog | null = null;

export function getPostHogServer() {
  if (!client && process.env.POSTHOG_API_KEY) {
    client = new PostHog(process.env.POSTHOG_API_KEY, {
      host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
      flushAt: 1,
      flushInterval: 0,
    });
  }
  return client;
}

Usage in API routes:

const posthog = getPostHogServer();
posthog?.capture({
  distinctId: userId,
  event: 'api_call',
  properties: { endpoint: '/api/generate', status: 200 },
});

Phase 3: Sentry Error Tracking

3.1 Install

Next.js:

npx @sentry/wizard@latest -i nextjs

This auto-creates sentry.client.config.ts, sentry.server.config.ts, sentry.edge.config.ts, and updates next.config.ts. Review the generated files.

React SPA (Vite):

npm install @sentry/react

Python (FastAPI):

pip install sentry-sdk[fastapi]

3.2 Configuration

Next.js — the wizard generates most of this. Review and adjust:

sentry.client.config.ts:

import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
  replaysSessionSampleRate: 0,
  replaysOnErrorSampleRate: 1.0,
  debug: false,
  enabled: process.env.NODE_ENV === 'production',
});

Key settings:

  • tracesSampleRate: 0.1 — sample 10% of transactions in prod (controls cost)
  • replaysOnErrorSampleRate: 1.0 — always capture session replay when errors happen
  • enabled: false in development — don't pollute Sentry with dev errors

React SPA:

import * as Sentry from '@sentry/react';

Sentry.init({
  dsn: import.meta.env.VITE_SENTRY_DSN,
  integrations: [
    Sentry.browserTracingIntegration(),
    Sentry.replayIntegration(),
  ],
  tracesSampleRate: 0.1,
  replaysSessionSampleRate: 0,
  replaysOnErrorSampleRate: 1.0,
  enabled: import.meta.env.PROD,
});

Python (FastAPI):

import sentry_sdk

sentry_sdk.init(
    dsn=os.getenv("SENTRY_DSN"),
    traces_sample_rate=0.1,
    profiles_sample_rate=0.1,
    environment=os.getenv("ENVIRONMENT", "development"),
    enabled=os.getenv("ENVIRONMENT") == "production",
)

3.3 Error Boundary (React)

Create a global error boundary that catches rendering errors:

Next.js App Routerapp/global-error.tsx:

'use client';

import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    Sentry.captureException(error);
  }, [error]);

  return (
    <html>
      <body>
        <div style={{ padding: '2rem', textAlign: 'center' }}>
          <h1>something went wrong</h1>
          <p style={{ color: '#666', marginTop: '0.5rem' }}>
            the error has been reported automatically.
          </p>
          <button
            onClick={reset}
            style={{ marginTop: '1rem', padding: '0.5rem 1rem', cursor: 'pointer' }}
          >
            try again
          </button>
        </div>
      </body>
    </html>
  );
}

Also add app/error.tsx for non-fatal page errors:

'use client';

import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    Sentry.captureException(error);
  }, [error]);

  return (
    <div style={{ padding: '2rem' }}>
      <h2>something went wrong</h2>
      <button onClick={reset}>try again</button>
    </div>
  );
}

3.4 Structured Error Logging

Create a helper for logging errors with context:

// lib/logger.ts
import * as Sentry from '@sentry/nextjs';

interface ErrorContext {
  userId?: string;
  action?: string;
  metadata?: Record<string, any>;
}

export function logError(error: unknown, context?: ErrorContext) {
  const err = error instanceof Error ? error : new Error(String(error));

  if (context) {
    Sentry.withScope((scope) => {
      if (context.userId) scope.setUser({ id: context.userId });
      if (context.action) scope.setTag('action', context.action);
      if (context.metadata) scope.setExtras(context.metadata);
      Sentry.captureException(err);
    });
  } else {
    Sentry.captureException(err);
  }

  // Also log to console in development
  if (process.env.NODE_ENV !== 'production') {
    console.error('[Error]', err.message, context);
  }
}

Usage:

try {
  await generateContent(prompt);
} catch (error) {
  logError(error, {
    userId: user.id,
    action: 'generate_content',
    metadata: { promptLength: prompt.length },
  });
}

Phase 4: Health Endpoint

A single endpoint that tells you if the app is alive. Use it for uptime monitoring (Vercel cron, UptimeRobot, Better Uptime).

Next.js — app/api/status/route.ts:

import { NextResponse } from 'next/server';

const startTime = Date.now();

export async function GET() {
  const uptime = Math.floor((Date.now() - startTime) / 1000);

  // Basic health check
  const health: Record<string, any> = {
    status: 'ok',
    uptime: `${uptime}s`,
    timestamp: new Date().toISOString(),
    environment: process.env.NODE_ENV || 'unknown',
    version: process.env.NEXT_PUBLIC_APP_VERSION || 'dev',
  };

  // Database check (if applicable)
  try {
    // Supabase
    if (process.env.NEXT_PUBLIC_SUPABASE_URL) {
      const { createClient } = await import('@supabase/supabase-js');
      const supabase = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL,
        process.env.SUPABASE_SERVICE_ROLE_KEY!
      );
      const { error } = await supabase.from('users').select('id').limit(1);
      health.database = error ? 'error' : 'ok';
    }

    // Prisma
    // const count = await prisma.user.count();
    // health.database = 'ok';
  } catch {
    health.database = 'error';
  }

  const isHealthy = health.database !== 'error';

  return NextResponse.json(health, {
    status: isHealthy ? 200 : 503,
  });
}

Python (FastAPI):

from datetime import datetime
import time

START_TIME = time.time()

@app.get("/api/status")
async def health_check():
    uptime = int(time.time() - START_TIME)
    return {
        "status": "ok",
        "uptime": f"{uptime}s",
        "timestamp": datetime.utcnow().isoformat(),
        "environment": os.getenv("ENVIRONMENT", "development"),
    }

What to monitor

Set up a cron or external service to hit /api/status every 5 minutes. Alert if:

  • Response status is not 200
  • Response time exceeds 5 seconds
  • Database field is "error"

Free options: UptimeRobot (free tier), Better Uptime, or Vercel's built-in cron.

Phase 5: Environment Variables

Add to .env.local (or .env):

# PostHog
NEXT_PUBLIC_POSTHOG_KEY=phc_...
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
POSTHOG_API_KEY=phc_...  # Same key, used server-side

# Sentry
NEXT_PUBLIC_SENTRY_DSN=https://xxx@o123.ingest.sentry.io/456
SENTRY_AUTH_TOKEN=sntrys_...  # For source map uploads
SENTRY_ORG=your-org
SENTRY_PROJECT=your-project

Verify none of these are committed to git. Check .gitignore includes .env*.

Phase 6: Verify

After wiring everything, test each system:

PostHog:
[ ] Open the app → check PostHog dashboard for a pageview event
[ ] Navigate between pages → each page triggers a pageview
[ ] Log in → user appears in PostHog with email/name (identified)
[ ] Track a custom event → appears in PostHog events
[ ] Log out → posthog.reset() called, new anonymous session

Sentry:
[ ] Add a temporary `throw new Error('sentry test')` to a page
[ ] Load the page → error appears in Sentry dashboard
[ ] Check that source maps resolve (you see your actual code, not minified)
[ ] Remove the test error
[ ] Error boundary renders fallback UI (not a white screen)

Health Endpoint:
[ ] GET /api/status returns 200 with uptime and database status
[ ] If database is down, returns 503
[ ] Set up monitoring service to ping it every 5 minutes

Tell the user which systems are live and working.

Important Notes

  • PostHog is free up to 1M events/month. More than enough for indie projects.
  • Sentry is free up to 5K errors/month. If you're hitting that, you have bigger problems.
  • Don't track PII in PostHog events (no passwords, no credit card numbers). User IDs and emails are fine.
  • Sample rates matter. 10% tracing in prod keeps costs near zero. Increase only if debugging specific issues.
  • Source maps are critical for Sentry. Without them, error stack traces show minified code. The Sentry wizard handles this for Next.js. For other frameworks, upload maps during build.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

Token Optimizer Pro

Monitor OpenClaw token usage, analyze usage patterns, estimate cost, and provide practical optimization suggestions. Use when reviewing token consumption by...

Registry SourceRecently Updated
1300Profile unavailable
Coding

DevOps Ops Bot

Server health monitoring with alerts and auto-recovery. Checks CPU, memory, disk, and uptime with configurable thresholds. Sends Slack/Discord alerts and can...

Registry SourceRecently Updated
1610Profile unavailable
Security

Darkhunt Observability

Monitor OpenClaw agent runs with real-time traces showing decisions, tool usage, performance, costs, and user attribution for full observability.

Registry SourceRecently Updated
1270Profile unavailable
Automation

Failure Memory

Stop making the same mistakes — turn failures into patterns that prevent recurrence

Registry SourceRecently Updated
5770Profile unavailable