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 apijQuery 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.