React

Set up Apperio in a React single-page app, with an error boundary.

This guide is for a React app that runs in the browser, such as one built with Vite. For Next.js, use the Next.js guide. The code works with React 18 and 19. Install the SDK first: npm install apperio.

1. Create the logger

Create one logger in its own module and import it wherever you need it:

src/apperio.ts
import { Apperio } from 'apperio';

export const logger = new Apperio({
  apiKey: 'your-api-key',
  projectId: 'your-project-id',
  environment: 'production',
  serviceName: 'web',
  release: '1.0.0',
});

Fill these in from your build’s environment variables. In Vite, for example, read import.meta.env.VITE_APPERIO_API_KEY and import.meta.env.MODE. The API key is public once it is in your bundle; that is expected.

2. Load it before rendering

Import the module at the top of your entry file, so the logger exists before React renders anything and catches errors from the very first render:

src/main.tsx
import './apperio';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
import { ErrorBoundary } from './ErrorBoundary';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <ErrorBoundary>
      <App />
    </ErrorBoundary>
  </StrictMode>,
);

Because the logger is created once at module level, Strict Mode’s double rendering doesn’t create a second one.

3. Report errors React catches

When an error boundary catches a rendering error, React shows the fallback and the error doesn’t reach the window, so the SDK never sees it. Log it from componentDidCatch:

src/ErrorBoundary.tsx
import { Component, type ErrorInfo, type ReactNode } from 'react';
import { logger } from './apperio';

interface Props {
  children: ReactNode;
}

interface State {
  hasError: boolean;
}

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

  static getDerivedStateFromError(): State {
    return { hasError: true };
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    logger.error('React render error', error, {
      componentStack: info.componentStack ?? undefined,
    });
  }

  render() {
    if (this.state.hasError) {
      return <p>Something went wrong. Please reload the page.</p>;
    }
    return this.props.children;
  }
}

The component stack goes with the error, so you can see which component failed. Errors outside rendering, such as in event handlers and timers, don’t go through boundaries; the SDK captures those itself.

4. Log from components

src/CheckoutButton.tsx
import { logger } from './apperio';

export function CheckoutButton({ orderId }: { orderId: string }) {
  const handleClick = async () => {
    logger.info('Checkout started', { orderId });
    try {
      await processOrder(orderId);
    } catch (err) {
      logger.error('Checkout failed', err instanceof Error ? err : new Error(String(err)), {
        orderId,
      });
    }
  };

  return <button onClick={handleClick}>Pay now</button>;
}

To tag every later log with the signed-in user, call logger.setContext({ userId }) after sign-in and logger.clearContext() after sign-out. Avoid emails and names: an ID is enough to find the user in your own systems.

Notes

  • Route changes made with React Router, or anything else that uses the History API, are logged as page views and stay in the same session.
  • Turn on session replay from the dashboard; no code change is needed.

Seeing it work

Throw an error from a button’s click handler in development, with environment set to production just for the test, and it appears in Issues within a few seconds.