Error Reporting

The Problem

Runtime errors can quietly break features and hurt conversion, but most error monitoring tools are expensive for smaller projects.

The Solution

Use 99Dev Custom Events to capture runtime errors in the same dashboard you already use for traffic analytics. It is a lightweight alternative to tools like Sentry or Bugsnag, with the same pricing model as other events: $1 per 7,000 page views and custom events combined.

Vanilla JavaScript / TypeScript

ES Module
main.ts
import { recordEvent } from "@99devco/analytics"

window.addEventListener("error", function (event) {
  recordEvent("Error", {
    message: event.message,
    source: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    stack: event.error?.stack,
    name: event.error?.name
  })
})

window.addEventListener("unhandledrejection", function (event) {
  const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason))

  recordEvent("Error", {
    message: error.message,
    source: "unhandledrejection",
    lineno: null,
    colno: null,
    stack: error.stack,
    name: error.name
  })
})

Script Tag
index.html
<script
  src="https://cdn.99.dev/analytics.js"
  data-site-uuid="<your-site-id>"
  async
></script>

<script>
window.addEventListener("error", function (event) {
  nndev.recordEvent("Error", {
    message: event.message,
    source: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    stack: event.error && event.error.stack,
    name: event.error && event.error.name
  })
})

window.addEventListener("unhandledrejection", function (event) {
  const reason = event.reason
  const error = reason instanceof Error ? reason : new Error(String(reason))

  nndev.recordEvent("Error", {
    message: error.message,
    source: "unhandledrejection",
    lineno: null,
    colno: null,
    stack: error.stack,
    name: error.name
  })
})
</script>

HTTP Errors

Browser runtime listeners (error and unhandledrejection) are great for JavaScript failures, but they do not automatically capture API responses like 404 or 500. Track those explicitly as HTTP Error events.

Fetch API
http-errors.ts
import { recordEvent } from "@99devco/analytics"

async function triggerHttpError () {
  const url = "https://httpbin.org/status/500"

  try {
    const response = await fetch(url)

    if (!response.ok) {
      const body = await response.text().catch(function () { return undefined })

      recordEvent("HTTP Error", {
        status: response.status,
        statusText: response.statusText || body || undefined,
        url: response.url,
        body: body || undefined
      })
    }
  } catch (err) {
    // Handles network-level failures (offline, DNS, CORS, etc)
    recordEvent("HTTP Error", {
      status: 0,
      statusText: "Network Error",
      url,
      message: err instanceof Error ? err.message : String(err)
    })
  }
}

Axios (Response Interceptor)
api.ts
import axios from "axios"
import { recordEvent } from "@99devco/analytics"

const api = axios.create({ baseURL: "/api" })

api.interceptors.response.use(
  function (response) {
    return response
  },
  function (error) {
    const response = error.response
    const data = response && response.data

    recordEvent("HTTP Error", {
      status: response ? response.status : 0,
      statusText: response ? response.statusText : "Network Error",
      url: response && response.config ? response.config.url : error.config?.url,
      body: typeof data === "string" ? data : data ? JSON.stringify(data) : undefined,
      message: error.message
    })

    return Promise.reject(error)
  }
)

export default api

jQuery AJAX (Global Handler)
index.html
<script
  src="https://cdn.99.dev/analytics.js"
  data-site-uuid="<your-site-id>"
  async
></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

<script>
$(document).ajaxError(function (_event, jqXHR, settings, thrownError) {
  nndev.recordEvent("HTTP Error", {
    status: jqXHR.status || 0,
    statusText: thrownError || jqXHR.statusText || "Network Error",
    url: settings && settings.url,
    body: jqXHR.responseText || undefined
  })
})
</script>

Angular HttpClient (Interceptor)
http-error.interceptor.ts
import { Injectable } from "@angular/core"
import {
  HttpErrorResponse,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest
} from "@angular/common/http"
import { Observable, throwError } from "rxjs"
import { catchError } from "rxjs/operators"
import { recordEvent } from "@99devco/analytics"

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
  intercept (req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(req).pipe(
      catchError(function (error: HttpErrorResponse) {
        recordEvent("HTTP Error", {
          status: error.status || 0,
          statusText: error.statusText || "Network Error",
          url: error.url || req.url,
          body: typeof error.error === "string" ? error.error : undefined,
          message: error.message
        })

        return throwError(function () { return error })
      })
    )
  }
}

React

Use an Error Boundary to catch render and lifecycle errors in component trees.

ErrorBoundary.tsx
import React from "react"
import { recordEvent } from "@99devco/analytics"

type Props = {
  children: React.ReactNode
}

type State = {
  hasError: boolean
}

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

  componentDidCatch (error: Error, info: React.ErrorInfo) {
    recordEvent("Error", {
      message: error.message,
      source: info.componentStack,
      lineno: null,
      colno: null,
      stack: error.stack,
      name: error.name
    })

    this.setState({ hasError: true })
  }

  render () {
    if (this.state.hasError) return <h1>Something went wrong.</h1>
    return this.props.children
  }
}

Next.js

React Error Boundaries work in Next.js too. In the App Router, you can also use app/error.tsx to report route-level errors.

app/error.tsx
"use client"

import { useEffect } from "react"
import { recordEvent } from "@99devco/analytics"

export default function Error ({
  error,
  reset
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(function () {
    recordEvent("Error", {
      message: error.message,
      source: "app/error.tsx",
      lineno: null,
      colno: null,
      stack: error.stack,
      name: error.name
    })
  }, [error])

  return (
    <div>
      <h2>Something went wrong.</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  )
}

Vue

Set a global error handler on your app instance.

main.ts
import { createApp } from "vue"
import { recordEvent } from "@99devco/analytics"
import App from "./App.vue"

const app = createApp(App)

app.config.errorHandler = function (error, instance, info) {
  recordEvent("Error", {
    message: error.message,
    source: info,
    lineno: null,
    colno: null,
    stack: error.stack,
    name: error.name,
    component: instance && instance.$options && instance.$options.name
  })
}

app.mount("#app")

Svelte

In SvelteKit, use the handleError hook on the client.

src/hooks.client.ts
import type { HandleClientError } from "@sveltejs/kit"
import { recordEvent } from "@99devco/analytics"

export const handleError: HandleClientError = ({ error, event, status, message }) => {
  recordEvent("Error", {
    message,
    source: event.url.pathname,
    lineno: null,
    colno: null,
    stack: error.stack,
    name: error.name,
    status
  })
}

Angular

Create a custom ErrorHandler and register it in your app providers.

error-handler.ts
import { ErrorHandler, Injectable } from "@angular/core"
import { recordEvent } from "@99devco/analytics"

@Injectable()
export class AppErrorHandler implements ErrorHandler {
  handleError (error: any): void {
    const typedError = error instanceof Error ? error : new Error(String(error))

    recordEvent("Error", {
      message: typedError.message,
      source: "Angular ErrorHandler",
      lineno: null,
      colno: null,
      stack: typedError.stack,
      name: typedError.name
    })

    console.error(typedError)
  }
}

WordPress

If your site uses the script tag install, you can add runtime error reporting directly in your theme footer.

footer.php
<script>
window.addEventListener("error", function (event) {
  nndev.recordEvent("Error", {
    message: event.message,
    source: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    stack: event.error && event.error.stack,
    name: event.error && event.error.name
  })
})

window.addEventListener("unhandledrejection", function (event) {
  const reason = event.reason
  const error = reason instanceof Error ? reason : new Error(String(reason))

  nndev.recordEvent("Error", {
    message: error.message,
    source: "unhandledrejection",
    lineno: null,
    colno: null,
    stack: error.stack,
    name: error.name
  })
})
</script>

The Result

All captured runtime errors appear in the Custom Events table on your 99Dev dashboard, so you can review real production issues alongside your traffic and event analytics.


Next
nil