Monitoring and Observability in the Frontend
Error monitoring and real user metrics in production — what to capture, what to avoid, and why frontend observability is harder than backend observability.
Tests tell you that the code worked when you ran it. Monitoring tells you whether it works for the users who are running it right now.
The gap between these two things is real, and in a large frontend system it's often where the most important bugs live. A browser quirk on a specific OS. A network timeout that doesn't happen on your machine. An interaction pattern that users actually use but that nobody tested. Production is a different environment than development, and you need eyes on it.
Error Monitoring
Sentry is the standard for frontend error monitoring. The core integration is simple:
// src/main.tsx
import * as Sentry from '@sentry/react';
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.MODE,
release: import.meta.env.VITE_APP_VERSION,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration({
maskAllText: true,
blockAllMedia: true,
}),
],
tracesSampleRate: 0.1, // 10% of transactions
replaysSessionSampleRate: 0.01, // 1% of sessions
replaysOnErrorSampleRate: 1.0, // 100% on error
});Once initialized, Sentry automatically captures unhandled JavaScript errors and unhandled promise rejections. The source maps tell it which line in your original TypeScript source corresponds to the minified production code.
For React, the error boundary integration captures component rendering errors:
function App() {
return (
<Sentry.ErrorBoundary
fallback={<ErrorPage />}
showDialog
>
<Router />
</Sentry.ErrorBoundary>
);
}Bugsnag is a close alternative — similar feature set, slightly different pricing model and team-notification ergonomics. The integration pattern is the same.
Source Maps in Production
Error monitoring without source maps tells you that something crashed in minified production code. With source maps, it tells you the original file, line, and function name.
There's a tradeoff: shipping source maps to the browser means your original source code is publicly accessible. The typical solution is to upload source maps to Sentry's servers but not deploy them to your CDN:
# In CI, after build
npx @sentry/cli sourcemaps upload \
--org my-org \
--project my-project \
./distSentry uses the source maps to symbolicate errors server-side. Your users never see them; you get line-level error reporting.
Real User Metrics
Error rates tell you when things break. Performance metrics tell you how your application feels to real users.
The Core Web Vitals (LCP, INP, CLS) are the standard frame. The web-vitals library reports these from real browser sessions:
import { onLCP, onINP, onCLS } from 'web-vitals';
function sendToAnalytics(metric: Metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
navigationType: metric.navigationType,
});
navigator.sendBeacon('/api/analytics', body);
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);The rating field classifies each measurement as good, needs-improvement, or poor based on the Web Vitals thresholds. Aggregating these in your analytics pipeline gives you a p75 view of what real users experience.
Sentry's performance monitoring also captures these automatically if you use browserTracingIntegration().
The PII Problem
Frontend monitoring is uniquely risky for PII (personally identifiable information). Backend logs are in a controlled environment. Frontend monitoring captures events from real user sessions — and those sessions contain user data.
A few concrete scenarios:
- Sentry's session replay captures user interactions. If a user fills out a form with their SSN and the request fails, the replay may contain that input.
- Error metadata can include query parameters, request bodies, or component props — any of which may contain PII.
- User identifiers attached to errors are useful for reproduction but may violate privacy regulations in some regions.
The defaults in most monitoring tools are conservative (maskAllText: true in Sentry's replay integration, for example), but you need to verify this for your specific use case. The configuration I showed above masks all text in session replays — a reasonable default if you can live with less detail in replays.
For GDPR compliance: if you're in the EU or have EU users, user consent for session replay is generally required. Cookie consent banners aren't just for cookies.
Before implementing monitoring that captures user session data, check with whoever owns legal/compliance at your organization. The conversation is worth having early rather than after a data incident.
What to Actually Alert On
Not every error needs a page. The signal-to-noise problem in frontend monitoring is real — a production React application on a diverse set of browsers will surface errors that are inconsequential (old browser quirks, browser extension interference, transient network conditions).
The error rates that matter:
- Error rate by user — how many users experienced at least one error this session? A spike here is meaningful regardless of total error count.
- Error rate on critical paths — checkout, auth, core functionality. These matter more than background errors.
- New errors — errors introduced by a recent deploy. Most monitoring tools can tag errors by release.
Setting up alerts based on these rates — rather than raw error counts — reduces noise and makes alerts actionable. An alert that fires every day loses its meaning quickly.
The Feedback Loop
The value of monitoring is the feedback loop it creates: you deploy, you see the effect in production metrics, you know whether the change made things better or worse. Without this, you're flying blind — deploying changes and hoping.
In a large team, this loop matters even more. Someone else's change affected your component's error rate. Without monitoring, you find out when users complain. With monitoring, you find out from a Slack notification before most users have even seen the issue.
Keep reading
Enjoyed this? Get more like it.
Deep dives on system design, React, web development, and personal finance — straight to your inbox. Free, always.