Skip to content
ExponaBLOG
REACT & NEXT.JS · DAY 061 / 100

Log frontend errors in production: a step-by-step guide

Users do not report bugs. They leave.

Expona Admin7 min read

Log frontend errors in production

Users do not report bugs. They leave. By the time you notice something is wrong, you've already lost conversions, damaged trust, and missed the critical window to fix it. That's why log frontend errors in production—not just in development—and make error visibility a first-class concern in your deployment pipeline.

I learned this the hard way. A trading platform I built had a subtle race condition in the WebSocket reconnection logic that only manifested under specific network conditions. Traders never complained. They just switched brokers. We only discovered it three weeks later by digging through browser console logs that one customer had screenshotted. We should have known about it in minutes.

Sentry or similar for errors and traces

The foundation of error visibility is a dedicated error tracking service. Sentry is the most mature choice for frontend, but LogRocket, Rollbar, and DataDog are solid alternatives. They capture uncaught exceptions, promise rejections, console errors, and network failures automatically, then aggregate them by error type, stack trace, and frequency.

Here's a minimal Next.js setup with Sentry:

tsx
// sentry.client.config.ts
import * as Sentry from "@sentry/nextjs";

export function initSentry() {
  Sentry.init({
    dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
    environment: process.env.NODE_ENV,
    tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
    integrations: [
      new Sentry.Replay({
        maskAllText: true,
        blockAllMedia: true,
      }),
    ],
    replaysSessionSampleRate: 0.1,
    replaysOnErrorSampleRate: 1.0,
  });
}

Call this in your root layout or app wrapper:

tsx
// app/layout.tsx
import { initSentry } from "@/sentry.client.config";

if (typeof window !== "undefined") {
  initSentry();
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    
      {children}
    
  );
}

Within minutes of deploying, you'll see real errors from real users. You'll discover that your date picker crashes on Firefox, that a payment button throws a JSON parse error under slow networks, or that your modal doesn't close properly on mobile. These are the gaps between your test environment and the wild.

Sentry also captures breadcrumbs—a timeline of user actions before the error occurred. If someone clicks "Buy," opens the cart, applies a coupon, and then the form fails, you see the exact sequence. This context is worth far more than a stack trace alone.

Upload source maps privately

By default, production builds are minified. A stack trace showing o.a() at line 1, col 45892 is useless. You need the original source code locations and variable names. That's what source maps do.

The catch: if you upload source maps to a public CDN alongside your JavaScript bundles, anyone can download them and reverse-engineer your code. The solution is to upload them directly to Sentry during your build, then keep them out of your public assets.

Add the Sentry CLI to your build:

tsx
// next.config.ts
import { withSentryConfig } from "@sentry/nextjs/cjs/config";

const nextConfig = {
  productionBrowserSourceMaps: true,
};

export default withSentryConfig(nextConfig, {
  org: process.env.SENTRY_ORG,
  project: process.env.SENTRY_PROJECT,
  authToken: process.env.SENTRY_AUTH_TOKEN,
  silent: false,
  widenClientFileUpload: true,
});

Set your environment variables in your CI/CD system:

tsx
SENTRY_ORG=your-org
SENTRY_PROJECT=your-project
SENTRY_AUTH_TOKEN=your-token

Now when you run npm run build in CI, the Sentry plugin automatically uploads source maps and strips them from your output. Users download small, minified bundles. Sentry sees the full, readable source. Your code stays yours.

Attach user and release context

An error without context is like a warning light with no dashboard. You need to know who the error happened to, when it happened (which release), and what conditions preceded it.

Set user context early, typically after login:

tsx
// lib/auth.ts
import * as Sentry from "@sentry/nextjs";

export function setUserContext(userId: string, email: string, name: string) {
  Sentry.setUser({
    id: userId,
    email,
    username: name,
  });
}

export function clearUserContext() {
  Sentry.setUser(null);
}

Call it in your login or session check:

tsx
// app/hooks/useSession.ts
import { useEffect } from "react";
import { useAuth } from "./useAuth";
import { setUserContext, clearUserContext } from "@/lib/auth";

export function useSessionContext() {
  const { user, isLoading } = useAuth();

  useEffect(() => {
    if (!isLoading) {
      if (user) {
        setUserContext(user.id, user.email, user.name);
      } else {
        clearUserContext();
      }
    }
  }, [user, isLoading]);
}

Sentry captures the release automatically from your package.json version, but be explicit:

tsx
// sentry.client.config.ts
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  release: process.env.NEXT_PUBLIC_APP_VERSION, // "1.2.3" from your deploy
  // ... rest of config
});

Now when an error occurs, you immediately know which user hit it, on which version, and whether this is a new regression or a recurring issue. If only one user in one region on one browser reports it, you can reproduce their exact conditions. If it affects 2,000 users, you declare an incident.

Alert on new error types after deploys

The most dangerous errors are the new ones you didn't expect. After you deploy, your error rate might stay flat overall, but you could have introduced three new error types that each affect 50 users. Without alerts, you won't know for hours.

Set up alert rules in Sentry to notify you when:

  • A new error type appears for the first time
  • Error frequency for a specific issue spikes above a threshold (e.g., 10 errors/minute)
  • An error with a specific tag (e.g., critical checkout flow) occurs
  • Your error rate crosses a percentage of total events (e.g., 5% of all requests fail)

Configure alerts via the Sentry UI or programmatically. Here's a real-world setup:

tsx
// Alert on new errors within 30 minutes of deploy
{
  "name": "New errors post-deploy",
  "conditions": [
    {
      "id": "sentry.rules.conditions.first_seen_event",
    },
    {
      "id": "sentry.rules.conditions.event_frequency",
      "value": 100,
      "match": "gt",
      "timeWindow": "5m",
    },
  ],
  "actions": [
    {
      "id": "sentry.integrations.slack",
      "channel": "#incidents",
    },
    {
      "id": "sentry.integrations.pagerduty",
      "severity": "critical",
    },
  ],
}

Send alerts to Slack, PagerDuty, or email so your team sees them immediately. Link directly to the error page so you can investigate without switching contexts. In my experience, acting within the first 15 minutes of a deploy-induced bug means a hotfix rollout, not a frustrated user base.

Common mistakes

  • Ignoring network errors. Sentry captures 404s and 500s from API calls by default, but only if you configure it. A backend endpoint that returns 500 consistently will tank your app silently if you don't log it. Always include integrations: [new Sentry.Fetch()] and new Sentry.XHR().
  • Setting sample rates too low. If you only capture 1% of errors in production, you'll miss entire categories of bugs. For low-traffic apps, capture 100%. For high-traffic, aim for at least 10% or set the sample rate per error type.
  • Uploading source maps during deploy. If your build server is offline when you deploy, source map upload fails silently and you're left with minified stack traces. Always upload source maps in your build step, before deployment.
  • Not filtering sensitive data. Credit cards, API keys, and passwords can end up in error messages or breadcrumbs. Use Sentry's beforeSend hook to scrub PII.
  • Alerting on every error. If you alert on every single error, you'll desensitize your team and noise will drown out real incidents. Use alert rules to filter for severity, not volume.

Key takeaways

  • Deploy error tracking (Sentry, LogRocket, Rollbar) as a dependency, not an afterthought. Users won't tell you about bugs—errors will.
  • Upload source maps directly to your error tracker during the build; keep them out of production assets. Readable stack traces are non-negotiable.
  • Set user and release context on every error so you can correlate failures to specific users and deployments. Context transforms noise into actionable intelligence.
  • Automate alerts for new error types and frequency spikes. A 15-minute response window for deploy-introduced bugs is the difference between a hotfix and a full incident.
  • Filter sensitive data and tune sample rates to balance cost with coverage. Cheap observability is worthless if you miss critical failures.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles