Observability
Lazy-loaded Sentry integration with global error capture and custom error reporting.
What Problem This Solves
You want error tracking in production without bloating your bundle with Sentry in development. This pattern lazy-loads Sentry only when VITE_SENTRY_DSN is present.
Prerequisites
- Optional:
@sentry/react
Files
observability.ts
ts
type ErrorCaptureFunction = (error: unknown, context?: string) => void;
let captureErrorFn: ErrorCaptureFunction = (error, context) => {
if (import.meta.env.DEV) {
const label = context ? `[${context}]` : "[error]";
console.error(label, error);
}
};
export const setErrorCaptureFunction = (fn: ErrorCaptureFunction) => {
captureErrorFn = fn;
};
export const captureError: ErrorCaptureFunction = (error, context) => {
captureErrorFn(error, context);
};
export const initObservability = async ({ sentryDsn }: { sentryDsn?: string }) => {
// Global error handlers (always active)
window.addEventListener("error", (event) => {
captureError(event.error, "window.error");
});
window.addEventListener("unhandledrejection", (event) => {
captureError(event.reason, "window.unhandledrejection");
});
// Lazy-load Sentry only if DSN provided
if (sentryDsn) {
try {
const Sentry = await import("@sentry/react");
Sentry.init({
dsn: sentryDsn,
tracesSampleRate: 0.1,
environment: import.meta.env.MODE,
});
captureErrorFn = (error, context) => {
Sentry.captureException(error, context ? { tags: { context } } : undefined);
};
} catch {
console.warn("Failed to initialize Sentry");
}
}
};Usage
1. Initialize on app boot
ts
// main.tsx
import { initObservability } from "./observability";
initObservability({
sentryDsn: import.meta.env.VITE_SENTRY_DSN,
});2. Capture errors manually
tsx
import { captureError } from "./observability";
async function riskyOperation() {
try {
await fetchData();
} catch (error) {
captureError(error, "DataFetch");
// Show user-friendly error...
}
}3. Use with Error Boundary
tsx
import { captureError } from "./observability";
export function ErrorFallback({ error }: { error: Error }) {
useEffect(() => {
captureError(error, "ErrorBoundary");
}, [error]);
return <div>Something went wrong.</div>;
}How It Works
- Always on: Global
window.errorandunhandledrejectionlisteners capture all uncaught errors - Dev mode: Errors are logged to console with context labels
- Production without Sentry: Errors are silently captured but not sent anywhere (you can add your own
setErrorCaptureFunction) - Production with Sentry: Sentry is dynamically imported, keeping it out of the initial bundle
Custom Backend
If you don't use Sentry, provide your own capture function:
ts
import { setErrorCaptureFunction } from "./observability";
setErrorCaptureFunction((error, context) => {
fetch("/api/errors", {
method: "POST",
body: JSON.stringify({
message: error instanceof Error ? error.message : String(error),
context,
stack: error instanceof Error ? error.stack : undefined,
}),
});
});