← Back to articles

How to Set Up Error Tracking (2026)

Production errors are inevitable. Error tracking ensures you know about them before your users complain. Here's how to implement it properly.

Why Error Tracking Matters

Console logs don't help in production. Error tracking captures:

  • Stack traces with source maps
  • User context (who experienced the error)
  • Breadcrumbs (what led to the error)
  • Environment and browser info
  • Error frequency and affected users

Option 1: Sentry (Recommended)

npm install @sentry/nextjs
npx @sentry/wizard@latest -i nextjs

Basic Setup

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  environment: process.env.NODE_ENV,
  
  // Sample rate (1.0 = 100% of errors)
  tracesSampleRate: 1.0,
  
  // Don't send errors in development
  enabled: process.env.NODE_ENV === 'production',
})

Adding Context

import * as Sentry from '@sentry/nextjs'

// Set user context after login
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.name,
})

// Add custom tags
Sentry.setTag('tenant_id', tenant.id)
Sentry.setContext('subscription', {
  plan: user.plan,
  status: user.subscriptionStatus,
})

Manual Error Capture

try {
  await riskyOperation()
} catch (error) {
  Sentry.captureException(error, {
    tags: { operation: 'payment_processing' },
    extra: { orderId, amount },
  })
  throw error // or handle gracefully
}

Breadcrumbs (What Led to the Error)

// Automatic breadcrumbs for:
// - Network requests
// - User clicks
// - Console logs
// - Navigation

// Manual breadcrumb
Sentry.addBreadcrumb({
  category: 'payment',
  message: 'User initiated checkout',
  level: 'info',
  data: { cartValue: 99.99 },
})

Option 2: BetterStack (Logs + Errors)

import { Logtail } from '@logtail/node'

const logtail = new Logtail(process.env.LOGTAIL_TOKEN)

logtail.error('Payment failed', {
  userId: user.id,
  orderId: order.id,
  error: error.message,
})

Server-Side Error Tracking (API Routes)

// app/api/posts/route.ts
import * as Sentry from '@sentry/nextjs'

export async function POST(req: Request) {
  try {
    const data = await req.json()
    const post = await createPost(data)
    return Response.json(post)
  } catch (error) {
    Sentry.captureException(error, {
      tags: { route: '/api/posts' },
      extra: { requestBody: await req.text() },
    })
    return Response.json(
      { error: 'Failed to create post' },
      { status: 500 }
    )
  }
}

React Error Boundaries

'use client'
import * as Sentry from '@sentry/nextjs'
import { useEffect } from 'react'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    Sentry.captureException(error)
  }, [error])

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

Source Maps (Critical!)

Without source maps, stack traces show minified code. Useless.

// next.config.js
module.exports = {
  sentry: {
    hideSourceMaps: true, // Don't expose to users
    widenClientFileUpload: true, // Upload all source maps
  },
}

Sentry automatically uploads source maps during build.

Alerts and Notifications

Sentry → Integrations → Slack

Configure:

  • New issue alert (first occurrence)
  • High-volume alert (>100 errors/hour)
  • Regression alert (error returns after being resolved)

Filtering Noise

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  
  beforeSend(event, hint) {
    // Ignore specific errors
    if (event.exception?.values?.[0]?.type === 'ChunkLoadError') {
      return null // Don't send to Sentry
    }
    
    // Filter by user
    if (event.user?.email?.includes('@yourcompany.com')) {
      return null // Ignore internal team errors
    }
    
    return event
  },
  
  ignoreErrors: [
    'ResizeObserver loop limit exceeded',
    'Non-Error promise rejection captured',
  ],
})

Performance Monitoring

import * as Sentry from '@sentry/nextjs'

const transaction = Sentry.startTransaction({
  name: 'Process Payment',
  op: 'payment',
})

try {
  const span = transaction.startChild({ op: 'stripe.charge' })
  await stripe.charges.create(...)
  span.finish()
} finally {
  transaction.finish()
}

FAQ

Do I need error tracking for a side project?

Yes. Use Sentry's free tier (5K errors/month). You'll find bugs you never knew existed.

Sentry vs BetterStack?

Sentry for rich error context and performance. BetterStack for logs + basic errors. Sentry is the gold standard.

Should I track errors in development?

No. Set enabled: process.env.NODE_ENV === 'production' to avoid noise.

Bottom Line

Set up Sentry today. It takes 5 minutes and catches production errors you'd never find otherwise. Add user context, configure source maps, and set up Slack alerts. The free tier (5K errors/month) covers most small projects.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.