sickn33/agentic-awesome-skills/frontend-observability

Claude Code and agent skill by sickn33/agentic-awesome-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

A portable, framework-agnostic field-side observability pattern for React or React Native integrations.

Install

npx vskill@latest install sickn33/agentic-awesome-skills/frontend-observability

SKILL.md

Frontend Observability (the field side)

When to Use

Use this skill when you need a portable, framework-agnostic field-side observability pattern for React or React Native integrations. Defines a typed taxonomy and best-effort provider dispatch with explicit consent, privacy and runtime limitations.

Portable skill — readable by Claude Code, OpenCode, Codex, Cursor, Windsurf, and others.
This skill describes a field-side observability system — event taxonomy, provider fan-out,
real-user vitals, error reporting, consent — not a dashboard or a specific vendor. It is the
field complement to the frontend-lighthouse skill: Lighthouse is the _lab_ gate (synthetic,
pre-merge); this is the _field_ (what real users actually experience). It lives in a
services/analytics/ module per the frontend-architecture skill.

The goal: you can answer "what are real users doing, and what are they experiencing?" — with a typed event vocabulary (no stringly-typed track("clicked_thing") scattered everywhere), a fan-out that is best-effort (provider exceptions are isolated where supported), real Core Web Vitals from the field, and consent respected before anything fires.


0. The five core ideas

  1. Events are a typed vocabulary. Event names are canonical constants with a union type — never inline string literals. The taxonomy is reviewable in one file and the compiler rejects typos.
  2. Fan-out is best-effort, not a scheduling guarantee. track() dispatches to every provider, each with synchronous and asynchronous error handling. A missing global, a thrown provider, an unloaded script — ordinary caught exceptions/rejections do not propagate; synchronous vendor work can still block the thread.
  3. One entry point, SSR-safe. A single track(event, props) is the only way to record. It's reached through a context hook that no-ops outside a provider and on the server, so instrumented components render safely anywhere.
  4. Field vitals complement lab budgets. Real-user LCP/INP/CLS are reported to the same fan-out. Lighthouse samples controlled conditions; field vitals estimate actual experiences for the observed population — together they close the loop.
  5. Consent gates everything. No telemetry (events, vitals, error reports with PII) fires before opt-in. Consent state is checked at the fan-out boundary, not sprinkled through call sites.

Runtime and privacy contract

These are independent integration sketches, not complete compiled modules. Supply the missing globals, config, imports, Props and error classifier; pin vendor APIs. Try/catch cannot prevent synchronous CPU blocking, infinite loops or SDK background effects. Adapters must do bounded work, return their promises, avoid raw logging and share per-provider rate/queue limits. Use actual keyboard/SSR/unmount/rejection tests.

Allowlist property names, types and sizes before dispatch; TypeScript types do not validate runtime input. IDs can identify people; URLs may contain tokens or query PII. Send a route/category label rather than a raw href, error message or stack. Treat cross-vendor fan-out as separate data disclosures requiring the approved purposes and destinations.

A boolean track gate does not stop SDK auto-collection, cookies, session replay or queued sends. Configure collection disabled before native SDK startup, delay scripts/init until permitted, disable and clear provider state/queues on withdrawal, and recheck consent after every awaited initialization. A dynamic import may still execute module effects; choose SDK loading that satisfies actual requirements. Consent storage failures default to denied. Do not hydrate a module-global consent state from user requests on SSR. React Server Components cannot call this client hook; conventional SSR only uses the no-op outside the provider. Native vitals are not web Core Web Vitals, and React Native Firebase requires a compatible native build rather than arbitrary Expo Go.

1. Directory layout

The system is one service module plus its constants (per frontend-architecture).

src/
├── constants/
│ └── analytics.ts ← canonical event names + AnalyticsEvent union
├── services/analytics/
│ ├── index.ts ← barrel: track, adapters, types
│ ├── track.ts ← the best-effort fan-out (single entry point)
│ ├── adapters.ts ← one (event, props) => void per provider, window-guarded
│ ├── web-vitals.ts ← report real-user LCP/INP/CLS into track()
│ └── consent.ts ← consent gate read by the fan-out
├── providers/
│ └── AnalyticsProvider.tsx ← 'use client' context exposing useAnalytics().track
└── error/
└── ErrorBoundary.tsx ← reports caught render errors via the fan-out

2. The event taxonomy (typed, never inline)

One file owns every event name. Components reference constants; the union type makes typos a compile error and the catalog a single source of truth.

ts
// constants/analytics.ts
export const ANALYTICS_EVENTS = {
PROJECT_CLICK: "project_click",
GITHUB_CLICK: "github_click",
RESUME_DOWNLOAD: "resume_download",
CONTACT_SUBMISSION: "contact_submission",
WEB_VITAL: "web_vital",
CLIENT_ERROR: "client_error",
} as const;
export type AnalyticsEvent =
(typeof ANALYTICS_EVENTS)[keyof typeof ANALYTICS_EVENTS];
ts
// CORRECT — typed constant, autocompletes, can't typo
track(ANALYTICS_EVENTS.GITHUB_CLICK, { url });
// WRONG — stringly-typed, drifts, no compile check
track("github-click"); // ❌ silently a different event from "github_click"

Hard rules:

  • No inline event-name strings anywhere; only ANALYTICS_EVENTS.*.
  • Event names are snake_case and stable — renaming one breaks historical dashboards, so treat the catalog as a contract.
  • Keep props shapes small and PII-light (see §6); prefer ids over names, never raw emails.

3. Best-effort fan-out with bounded adapter work

track() is the single entry point. It iterates the adapter registry, guarding each call so one provider can't affect the caller or the others.

ts
// services/analytics/track.ts
import type { AnalyticsEvent } from "@/constants/analytics";
import { analyticsAdapters } from "./adapters";
import { hasConsent } from "./consent";
export function track(
event: AnalyticsEvent,
props?: Record<string, unknown>,
): void {
if (!hasConsent()) return; // §6 — nothing fires before opt-in
for (const adapter of analyticsAdapters) {
try {
void Promise.resolve(adapter(event, props)).catch(() => {
// Drop provider failures; never recurse into analytics or log raw data.
});
} catch {
/* best-effort: a failing/absent provider must never throw into the
caller or block dispatch to the remaining providers. */
}
}
}

Each adapter is a tiny (event, props) => void that guards its provider global — it no-ops on the server (no window) and when the provider script is absent, so a missing or unloaded provider never throws.

ts
// services/analytics/adapters.ts
import type { AnalyticsEvent } from "@/constants/analytics";
// Supply AnalyticsGlobals type declarations and a platform-specific firebaseAdapter import.
export type AnalyticsAdapter = (
event: AnalyticsEvent,
props?: Record<string, unknown>,
) => void | Promise<void>;
export const googleAnalyticsAdapter: AnalyticsAdapter = (event, props) => {
const w =
typeof window !== "undefined" ? (window as AnalyticsGlobals) : undefined;
if (!w || typeof w.gtag !== "function") return; // SSR-safe + absent-safe
w.gtag("event", event, props ?? {});
};
export const clarityAdapter: AnalyticsAdapter = (event) => {
const w =
typeof window !== "undefined" ? (window as AnalyticsGlobals) : undefined;
if (!w || typeof w.clarity !== "function") return;
w.clarity("event", event);
};
// The registry track() fans out across. Exported + mutable so tests can swap
// in a recording sink to assert dispatch.
export const analyticsAdapters: AnalyticsAdapter[] = [
googleAnalyticsAdapter,
clarityAdapter,
firebaseAdapter,
// posthogAdapter, openPanelAdapter, …
];
3.1 Firebase Analytics — one adapter, two platforms

Firebase Analytics ships two SDKs that share the same `logEvent(name, params)` contract, so a single conceptual adapter covers both web and React Native — only the import and the "is it available?" guard differ. On web the adapter never imports the SDK at module top level (it's browser-only and async), so it stays SSR-safe.

ts
// services/analytics/adapters.firebase.web.ts — Firebase JS SDK (web)
import type { Analytics } from "firebase/analytics";
import { hasConsent } from "./consent";
import type { AnalyticsAdapter } from "./adapters";
// Held after a lazy, browser-only init (below) so the adapter stays synchronous + SSR-safe.
let analytics: Analytics | undefined;
export function setFirebaseAnalytics(instance: Analytics): void {
analytics = instance;
}
export const firebaseAdapter: AnalyticsAdapter = (event, props) => {
if (typeof window === "undefined" || !analytics) return; // SSR-safe + not-yet-ready safe
return import("firebase/analytics").then(({ logEvent }) => {
if (!hasConsent() || !analytics) return; // recheck after async work
logEvent(analytics, event, props);
});
};
ts
// services/analytics/firebase.init.ts — lazy, browser-only init (web)
import { initializeApp, getApps } from "firebase/app";
import { hasConsent } from "./consent";
import { setFirebaseAnalytics } from "./adapters.firebase.web";
import { FIREBASE_CONFIG } from "@/constants/analytics";
export async function initFirebaseAnalytics(): Promise<void> {
if (typeof window === "undefined") return; // never on the server
if (!hasConsent()) return;
const { getAnalytics, isSupported } = await import("firebase/analytics");
if (!(await isSupported()) || !hasConsent()) return; // unsupported browser → no-op
const app = getApps()[0] ?? initializeApp(FIREBASE_CONFIG);
setFirebaseAnalytics(getAnalytics(app)); // adapter goes live after this
}
ts
// services/analytics/adapters.firebase.native.ts — @react-native-firebase/analytics (RN / Expo)
import analytics from "@react-native-firebase/analytics";
import type { AnalyticsAdapter } from "./adapters";
export const firebaseAdapter: AnalyticsAdapter = (event, props) => {
// RN: no window; the native module is present once the app boots.
return analytics().logEvent(event, props);
};

Same shape, two files. Resolve the platform variant by file extension (adapters.firebase.native.ts via Metro's .native.ts resolution, or a Platform.OS switch) so the registry, `track` fan-out, consent gate, taxonomy, and `useAnalytics` hook never change across platforms. Verify Firebase event-name/parameter restrictions and reserved names for the pinned SDK. Snake_case is this taxonomy convention, not the full provider validation contract. Gate initFirebaseAnalytics() on consent (§6) — Firebase also exposes setAnalyticsCollectionEnabled(false) to harden the opt-out.

Why this shape: analytics is the _last_ thing that should crash an app. A vendor script that fails to load, a global that isn't there yet, an adapter that throws on a malformed prop — all are contained. The registry being exported and mutable makes dispatch unit-testable without mounting any provider.


4. The provider + hook (SSR-safe entry)

A 'use client' context exposes track through useAnalytics(). Outside a provider (tests, server) it returns a no-op, so instrumented components never throw in isolation.

tsx
// providers/AnalyticsProvider.tsx
"use client";
import { createContext, useContext, useMemo, type ReactNode } from "react";
import { track as trackEvent } from "@/services/analytics";
import type { AnalyticsEvent } from "@/constants/analytics";

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
48,922
Repository forks
7,100
7-day trend score
54

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/sickn33/agentic-awesome-skills/frontend-observability/badge)](https://verified-skill.com/skills/sickn33/agentic-awesome-skills/frontend-observability)

Something wrong with this skill or its page? Tell us.