Skip to content
fullstory-component-wellbeing logo

Fullstory Component Wellbeing & Performance

fullstory-component-wellbeing

Expert guidance for monitoring frontend component health, performance, and rendering stability within Fullstory. Framework-agnostic patterns for React, Vue, Angular, Svelte, and React Native.

SKILL.md

Full skill instructions

Fullstory Component Wellbeing & Performance

💡 STRATEGY: Application "wellbeing" is defined by the absence of frustration signals (Rage Clicks, Dead Clicks) and the presence of stable, performant component lifecycles. This skill bridges your framework's internal health with Fullstory's external observability.

Overview

This skill provides patterns to expose framework-specific performance bottlenecks and application errors to Fullstory. The goal is to ensure that when a "Rage Click" occurs, developers can see the exact component state and any underlying "silent" failures—regardless of whether you're using React, Vue, Angular, Svelte, or React Native.

Core Concepts

1. Universal Semantic Attributes

All frameworks render to DOM (or native views). Use data-* attributes as your stable contract with Fullstory:

AttributePurposeWorks In
data-componentStable anchor for heatmaps and searchAll frameworks
data-stateTracks loading, error, ready, staleAll frameworks
data-testidAlternative stable selectorAll frameworks

2. Error Boundary Pattern (Framework-Specific)

Every framework has a way to catch rendering/​component errors. The pattern is the same: catch the error, extract context, send to Fullstory.

3. Render Performance Thresholds

Instead of tracking every render (noise), track when renders exceed acceptable thresholds using the Performance API.

┌──────────────────────────────────────────────────────────────┐
│ Component Tree (Any Framework)                                │
│ ├─ <App>                                                      │
│ │  └─ <ErrorBoundary> ──► Reports crash to FS                │
│ │     └─ <Dashboard>                                          │
│ │        └─ <Chart data-state="slow" data-component="Chart">  │
└──────────────────────────────────────────────────────────────┘

API Reference: Semantic Wellbeing

Attribute / MethodTargetPurpose
data-componentAny ComponentCreates a stable anchor for heatmaps and performance search
data-stateDynamic ContainersTracks states like loading, error, ready, or stale
FS('setVars')Error HandlersPasses component context to Fullstory for debugging
FS('event')Error HandlersLogs framework errors for proactive alerting

✅ GOOD Implementation Examples

Example 1: Global Error Handler

Vanilla JavaScript
// Works without any framework - catches all uncaught errors
window.addEventListener('error', (event) => {
  FS('setVars', {
    scope: 'page',
    vars: {
      last_error_source: event.filename || 'unknown',
      app_wellbeing_status: 'degraded'
    }
  });
  FS('event', 'Uncaught Error', {
    framework: 'vanilla',
    error_message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno
  });
});

// Also catch unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
  FS('setVars', {
    scope: 'page',
    vars: {
      app_wellbeing_status: 'degraded'
    }
  });
  FS('event', 'Unhandled Promise Rejection', {
    framework: 'vanilla',
    error_message: event.reason?.message || String(event.reason)
  });
});
React (Class or Function Components)
class FullstoryErrorBoundary extends React.Component {
  componentDidCatch(error, info) {
    FS('setVars', {
      scope: 'page',
      vars: {
        last_error_component: info.componentStack?.split("\n")[1]?.trim() || 'unknown',
        app_wellbeing_status: 'degraded'
      }
    });
    FS('event', 'Component Crash', {
      framework: 'react',
      error_message: error.message,
      component_stack: info.componentStack
    });
  }
  render() { return this.props.children; }
}
Vue 3
// main.js or plugin
app.config.errorHandler = (error, instance, info) => {
  const componentName = instance?.$options?.name || instance?.$.type?.name || 'unknown';
  
  FS('setVars', {
    scope: 'page',
    vars: {
      last_error_component: componentName,
      app_wellbeing_status: 'degraded'
    }
  });
  FS('event', 'Component Crash', {
    framework: 'vue',
    error_message: error.message,
    component_name: componentName,
    lifecycle_hook: info
  });
};
Angular
// global-error-handler.ts
@Injectable()
export class FullstoryErrorHandler implements ErrorHandler {
  handleError(error: Error): void {
    const componentName = this.extractComponentName(error);
    
    FS('setVars', {
      scope: 'page',
      vars: {
        last_error_component: componentName,
        app_wellbeing_status: 'degraded'
      }
    });
    FS('event', 'Component Crash', {
      framework: 'angular',
      error_message: error.message,
      stack: error.stack
    });
  }
  
  private extractComponentName(error: any): string {
    // Angular errors often contain component context in the message
    const match = error.message?.match(/​at (\w+Component)/);
    return match?.[1] || 'unknown';
  }
}

// app.module.ts
providers: [{ provide: ErrorHandler, useClass: FullstoryErrorHandler }]
Svelte
// hooks.client.js (SvelteKit) or main.js
import { onMount } from 'svelte';

// Global error handler for Svelte apps
window.addEventListener('error', (event) => {
  FS('setVars', {
    scope: 'page',
    vars: {
      last_error_source: event.filename || 'unknown',
      app_wellbeing_status: 'degraded'
    }
  });
  FS('event', 'Component Crash', {
    framework: 'svelte',
    error_message: event.message,
    filename: event.filename
  });
});

// For SvelteKit, use the handleError hook in hooks.client.js
export function handleError({ error, event }) {
  FS('setVars', {
    scope: 'page',
    vars: {
      last_error_route: event.url.pathname,
      app_wellbeing_status: 'degraded'
    }
  });
  FS('event', 'Component Crash', {
    framework: 'svelte',
    meta_framework: 'sveltekit',
    error_message: error.message,
    route: event.url.pathname
  });
}
React Native
// Same as React, plus native crash handling
class FullstoryErrorBoundary extends React.Component {
  componentDidCatch(error, info) {
    FS('setVars', {
      scope: 'page',
      vars: {
        last_error_component: info.componentStack?.split("\n")[1]?.trim() || 'unknown',
        app_wellbeing_status: 'degraded'
      }
    });
    FS('event', 'Component Crash', {
      framework: 'react-native',
      error_message: error.message,
      component_stack: info.componentStack
    });
  }
  render() { return this.props.children; }
}

Example 2: Hydration/​SSR Health

React (Next.js / Remix)
useEffect(() => {
  const root = document.getElementById('__next') || document.getElementById('root');
  if (root && !root.hasChildNodes()) {
    FS('event', 'Hydration Failure', { 
      framework: 'react',
      meta_framework: 'nextjs',
      path: window.location.pathname
    });
  }
}, []);
Vue (Nuxt)
// plugins/​hydration-check.client.js
export default defineNuxtPlugin(() => {
  if (import.meta.client) {
    const root = document.getElementById('__nuxt');
    if (root && root.dataset.serverRendered && !root.hasChildNodes()) {
      FS('event', 'Hydration Failure', {
        framework: 'vue',
        meta_framework: 'nuxt',
        path: window.location.pathname
      });
    }
  }
});
Angular (Universal)
// app.component.ts
@Component({ ... })
export class AppComponent implements AfterViewInit {
  constructor(@Inject(PLATFORM_ID) private platformId: Object) {}
  
  ngAfterViewInit() {
    if (isPlatformBrowser(this.platformId)) {
      const state = document.querySelector('[ng-server-context]');
      if (state && document.body.children.length === 0) {
        FS('event', 'Hydration Failure', {
          framework: 'angular',
          meta_framework: 'universal',
          path: window.location.pathname
        });
      }
    }
  }
}
Svelte (SvelteKit)
// +layout.svelte or +page.svelte
import { onMount } from 'svelte';
import { browser } from '$app/​environment';

onMount(() => {
  if (browser) {
    const root = document.getElementById('svelte');
    // Check if hydration produced expected content
    if (root && root.children.length === 0) {
      FS('event', 'Hydration Failure', {
        framework: 'svelte',
        meta_framework: 'sveltekit',
        path: window.location.pathname
      });
    }
  }
});

Example 3: Semantic State Attributes (Universal)

Works identically across all frameworks—just different template syntax:

Vanilla JavaScript
// Using DOM manipulation
const container = document.querySelector('#search-results');
container.setAttribute('data-component', 'SearchResults');

function updateState(isSearching, results) {
  const state = isSearching ? 'loading' : results.length === 0 ? 'empty' : 'ready';
  container.setAttribute('data-state', state);
}
React / React Native
const SearchResults = ({ results, isSearching }) => (
  <div 
    data-component="SearchResults" 
    data-state={isSearching ? 'loading' : results.length === 0 ? 'empty' : 'ready'}
  >
    {/​* content */​}
  </​div>
);
Vue
<template>
  <div 
    data-component="SearchResults" 
    :data-state="computedState"
  >
    <!-- content -->
  </​div>
</​template>

<script setup>
const computedState = computed(() => 
  isSearching.value ? 'loading' : results.value.length === 0 ? 'empty' : 'ready'
);
</​script>
Angular
<div 
  data-component="SearchResults" 
  [attr.data-state]="isSearching ? 'loading' : results.length === 0 ? 'empty' : 'ready'"
>
  <!-- content -->
</​div>
Svelte
<div 
  data-component="SearchResults" 
  data-state={isSearching ? 'loading' : results.length === 0 ? 'empty' : 'ready'}
>
  <!-- content -->
</​div>

Example 4: Long-Task Render Warning (Universal)

The Performance API works in all browser-based frameworks:

Vanilla JavaScript
// Generic performance wrapper for any operation
function trackPerformance(operationName, fn) {
  const start = performance.now();
  const result = fn();
  const duration = performance.now() - start;
  
  if (duration > 50) {
    FS('event', 'Long Operation Detected', { 
      operationName, 
      duration, 
      framework: 'vanilla' 
    });
  }
  return result;
}

// Usage
trackPerformance('renderTable', () => {
  // DOM manipulation here
});
React
function usePerformanceTag(componentName) {
  useEffect(() => {
    const start = performance.now();
    return () => {
      const duration = performance.now() - start;
      if (duration > 50) {
        FS('event', 'Long Render Detected', { componentName, duration, framework: 'react' });
      }
    };
  });
}
Vue
// composables/​usePerformanceTag.js
export function usePerformanceTag(componentName) {
  let start;
  
  onMounted(() => { start = performance.now(); });
  
  onUpdated(() => {
    const duration = performance.now() - start;
    if (duration > 50) {
      FS('event', 'Long Render Detected', { componentName, duration, framework: 'vue' });
    }
    start = performance.now();
  });
}
Angular
@Directive({ selector: '[fsPerformanceTag]' })
export class PerformanceTagDirective implements AfterViewInit, AfterViewChecked {
  @Input('fsPerformanceTag') componentName: string;
  private start: number;
  
  ngAfterViewInit() { this.start = performance.now(); }
  
  ngAfterViewChecked() {
    const duration = performance.now() - this.start;
    if (duration > 50) {
      FS('event', 'Long Render Detected', { 
        componentName: this.componentName, 
        duration, 
        framework: 'angular' 
      });
    }
    this.start = performance.now();
  }
}
Svelte
<script>
  import { onMount, afterUpdate } from 'svelte';
  
  export let componentName;
  let start;
  
  onMount(() => { start = performance.now(); });
  
  afterUpdate(() => {
    const duration = performance.now() - start;
    if (duration > 50) {
      FS('event', 'Long Render Detected', { componentName, duration, framework: 'svelte' });
    }
    start = performance.now();
  });
</​script>

Example 5: Portal/​Overlay Visibility

React
const Modal = ({ isOpen, children }) => isOpen ? ReactDOM.createPortal(
  <div data-component="GlobalModal" data-state="visible">{children}</​div>,
  document.body
) : null;
Vue
<Teleport to="body" v-if="isOpen">
  <div data-component="GlobalModal" data-state="visible">
    <slot />
  </​div>
</​Teleport>
Angular
// Using CDK Portal
@Component({
  template: `
    <ng-template cdkPortal>
      <div data-component="GlobalModal" data-state="visible">
        <ng-content></​ng-content>
      </​div>
    </​ng-template>
  `
})
export class ModalComponent { }
Svelte
<script>
  import { portal } from 'svelte-portal';
  export let isOpen = false;
</​script>

{#if isOpen}
  <div use:portal={'body'} data-component="GlobalModal" data-state="visible">
    <slot />
  </​div>
{/​if}
React Native
// React Native uses Modal component, not portals
<Modal visible={isOpen} data-component="GlobalModal" data-state="visible">
  {children}
</​Modal>

Example 6: Decoupled API Health (Universal)

Tracking when a component is waiting for data vs. a failure:

Vanilla JavaScript
const container = document.querySelector('#user-profile');
container.setAttribute('data-component', 'UserProfile');

async function loadProfile(userId) {
  container.setAttribute('data-state', 'loading');
  try {
    const user = await fetchUser(userId);
    container.setAttribute('data-state', 'ready');
    renderProfile(user);
  } catch (error) {
    container.setAttribute('data-state', 'error');
    renderError(error.message);
  }
}
React
const Profile = ({ user, error }) => (
  <div 
    data-component="UserProfile" 
    data-state={error ? 'error' : user ? 'ready' : 'loading'}
  >
    {error ? <ErrorMessage message={error} /> : <UserDetail user={user} />}
  </​div>
);
Vue
<template>
  <div 
    data-component="UserProfile" 
    :data-state="error ? 'error' : user ? 'ready' : 'loading'"
  >
    <ErrorMessage v-if="error" :message="error" />
    <UserDetail v-else :user="user" />
  </​div>
</​template>
Angular
<div 
  data-component="UserProfile" 
  [attr.data-state]="error ? 'error' : user ? 'ready' : 'loading'"
>
  <app-error-message *ngIf="error" [message]="error"></​app-error-message>
  <app-user-detail *ngIf="!error" [user]="user"></​app-user-detail>
</​div>
Svelte
<div 
  data-component="UserProfile" 
  data-state={error ? 'error' : user ? 'ready' : 'loading'}
>
  {#if error}
    <ErrorMessage message={error} />
  {:else}
    <UserDetail {user} />
  {/​if}
</​div>

❌ BAD Implementation Examples

Example 1: Dumping Raw State (All Frameworks)

Why it's bad: Creates massive noise and risks leaking PII.

// BAD: Do not dump entire state object into Fullstory

// Vanilla JS
const state = getAppState();
FS('setVars', { scope: 'page', vars: { ...state } });

// React
const { state } = useMyState();
useEffect(() => {
  FS('setVars', { scope: 'page', vars: { ...state } });
}, [state]);

// Vue
watch(state, (newState) => {
  FS('setVars', { scope: 'page', vars: { ...newState } });
});

// Angular
this.store.subscribe(state => {
  FS('setVars', { scope: 'page', vars: { ...state } });
});

// Svelte
$: FS('setVars', { scope: 'page', vars: { ...$store } });

Example 2: Unstable Selectors (All Frameworks)

Why it's bad: Framework-generated class names change every build, breaking Fullstory Search.

// BAD: These selectors are brittle
// Vanilla (CSS-in-JS): <div class="css-abc123">
// React (styled-components): <div class="sc-ax123">
// Vue (scoped): <div class="data-v-abc123">
// Angular (ViewEncapsulation): <div class="_ngcontent-abc-123">
// Svelte (scoped): <div class="svelte-xyz789">

// GOOD: Use data-component for stable identification
<div data-component="SearchResults">...</​div>

Example 3: Redundant Click Events (All Frameworks)

Why it's bad: Fullstory autocaptures clicks; manual events create redundant data.

// BAD: Manual event tracking for basic interactions

// Vanilla
button.addEventListener('click', () => {
  FS('event', 'Login Button Clicked'); // Redundant!
  handleLogin();
});

// React
<button onClick={() => {
  FS('event', 'Login Button Clicked'); // Redundant! FS already knows this.
  handleLogin();
}}>Login</​button>

// Vue
<button @click="FS('event', 'Login Button Clicked'); handleLogin()">Login</​button>

// Angular
<button (click)="logClick(); handleLogin()">Login</​button>

// Svelte
<button on:click={() => { FS('event', 'Login Button Clicked'); handleLogin(); }}>Login</​button>

Example 4: Prop-Drilling FS Instance (React/​Vue/​Svelte)

Why it's bad: The FS global is already available. Prop-drilling makes components harder to test and maintain.

// BAD: Unnecessary prop complexity
const DeepChild = ({ fsInstance }) => (
  <button onClick={() => fsInstance('event', 'child_click')}>Submit</​button>
);

Example 5: High-Frequency Updates (All Frameworks)

Why it's bad: Will hit FS rate limits or crash the browser tab.

// BAD: Flooding the API with high-frequency updates
window.addEventListener('mousemove', (e) => {
  FS('setVars', { scope: 'page', vars: { mouseX: e.clientX } });
});

// React
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
  FS('setVars', { scope: 'page', vars: { mouse_x: pos.x } });
}, [pos]);

// Svelte
$: FS('setVars', { scope: 'page', vars: { mouse_x: mouseX } }); // Reactive on every change!

Example 6: Over-Masking Root (All Frameworks)

Why it's bad: Masking the root element makes session replay useless for diagnosing wellbeing.

<!-- BAD: Don't mask the root! Use targeted masking instead. -->

<!-- Vanilla -->
<div id="app" class="fs-mask">...</​div>

<!-- React -->
<div id="root" class="fs-mask">...</​div>

<!-- Vue -->
<div id="app" class="fs-mask">...</​div>

<!-- Angular -->
<app-root class="fs-mask">...</​app-root>

<!-- Svelte -->
<div id="svelte" class="fs-mask">...</​div>

Key Takeaways for Agent

  1. Prioritize Error Boundaries/​Handlers: Always recommend framework-appropriate error catching that calls FS('setVars') and FS('event').

  2. Stable Selectors for Performance: Use data-component attributes. Fullstory can then aggregate "Time to Interactive" for specific components.

  3. Monitor "Loading" States: Use data-state="loading". This allows searching for sessions where users waited for content for more than N seconds.

  4. Avoid Redundancy: Don't manually log clicks or navigation unless adding specific metadata that autocapture misses.

  5. Hydration Awareness: If using SSR frameworks (Next.js, Nuxt, SvelteKit, Angular Universal), log hydration failures as events to detect "broken" entry points.

  6. Performance API is Universal: performance.now() works in all browser environments for measuring render times.

// Quick Agent logic check:
if (isFrontendApp && isPerformanceIssue) {
  recommend("Stable Selectors (data-component) on suspected bottlenecks");
  recommend("Global error handler with framework-specific API");
  recommend("data-state='loading' for measuring async delays");
}

Framework Error Handler Quick Reference

FrameworkError HandlerComponent Context
Vanilla JSwindow.addEventListener('error')event.filename
ReactcomponentDidCatch(error, info)info.componentStack
Vue 3app.config.errorHandlerinstance.$options.name
AngularErrorHandler.handleError()Parse from error message
SveltehandleError hook (SvelteKit)event.url.pathname
React NativecomponentDidCatch(error, info)info.componentStack

Fullstory Search Examples

Find Sessions with Component Crashes (Any Framework)

Event where name = "Component Crash"

Filter by Framework

Event where name = "Component Crash" and framework = "vue"
Event where name = "Component Crash" and framework = "svelte"

Find Sessions with Degraded Wellbeing

Page where app_wellbeing_status = "degraded"

Find Components Stuck in Loading State

Clicked on element where data-state = "loading"

Find Long Renders

Event where name = "Long Render Detected" and duration > 100

Find Hydration Failures

Event where name = "Hydration Failure"

Find Hydration Failures by Meta-Framework

Event where name = "Hydration Failure" and meta_framework = "nuxt"
Event where name = "Hydration Failure" and meta_framework = "sveltekit"

More skills from diegosouzapw

accessibility logo
diegosouzapw/awesome-omni-skill

accessibility

Accessibility guidelines for VS Code features — covers accessibility help dialogs, accessible views, verbosity settings, accessibility signals, ARIA alerts/status announcements, keyboard navigation, and ARIA labels/roles. Applies to both new interactive UI surfaces and updates to existing feature...

62 1
View
mermaid-diagrams logo
diegosouzapw/awesome-omni-skill

mermaid-diagrams

Mermaid diagram creation for flowcharts, sequences, ERDs, and more. Generate diagrams from text in markdown files. Use for documentation, architecture diagrams, and visual representations. Triggers on mermaid, flowchart, sequence diagram, ERD, entity relationship, gantt chart, pie chart, class di...

62 1
View
tauri2-react-rust logo
diegosouzapw/awesome-omni-skill

tauri2-react-rust

Guides development of cross-platform desktop apps with Tauri 2, TypeScript, React, and Rust. Use when building Tauri apps, implementing IPC, designing Rust backend or TypeScript/React frontend, when researching or cloning a website (open site, snapshot elements), when verifying local dev or built...

62 1
View
fp-ts-errors logo
diegosouzapw/awesome-omni-skill

fp-ts-errors

Handle errors as values using fp-ts Either and TaskEither for cleaner, more predictable TypeScript code. Use when implementing error handling patterns with fp-ts.

62 0
View
ring:pre-dev-research logo
diegosouzapw/awesome-omni-skill

ring:pre-dev-research

Gate 0 research phase for pre-dev workflow. Dispatches 3 parallel research agents to gather codebase patterns, external best practices, and framework documentation BEFORE creating PRD/TRD. Outputs research.md with file:line references.

62 0
View
create-assistant logo
diegosouzapw/awesome-omni-skill

create-assistant

Create and configure Vapi voice AI assistants with models, voices, transcribers, tools, hooks, and advanced settings. Use when building voice agents, phone bots, customer support assistants, or any conversational AI that handles phone or web calls.

62 0
View
codex-headless logo
diegosouzapw/awesome-omni-skill

codex-headless

Delegiere Aufgaben an OpenAI Codex CLI im Headless-Mode. Nutzt ChatGPT Subscription (KEIN API Key). Nur Workspace-Zugriff, KEIN System-Zugriff. Auto-Accept mit --full-auto. Web-Recherche mit --search.

62 0
View
social-media-scheduler logo
diegosouzapw/awesome-omni-skill

social-media-scheduler

Generate a full week of social media content for any topic. Outputs platform-optimized posts for Twitter/X, LinkedIn, and Instagram with hashtags and posting times.

62 0
View
Data Privacy Compliance logo
diegosouzapw/awesome-omni-skill

Data Privacy Compliance

Data privacy and regulatory compliance specialist for GDPR, CCPA, HIPAA, and international data protection laws. Use when implementing privacy controls, conducting data protection impact assessments, ensuring regulatory compliance, or managing data subject rights. Expert in consent management, da...

62 0
View
triple-review logo
diegosouzapw/awesome-omni-skill

triple-review

Reviews a task using the TRIPLE protocol, as the review agent.

62 0
View
Tech Stack & Architecture Decision logo
diegosouzapw/awesome-omni-skill

Tech Stack & Architecture Decision

Define and document the technology stack and architecture decisions for a project. Use when the user needs to choose a tech stack, make architecture decisions, define infrastructure choices, or document technology selections. Triggers on requests like "define the tech stack", "choose technologies...

62 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View