DEV Community

Carlos Oliva Pascual
Carlos Oliva Pascual

Posted on • Originally published at stacknotice.com

Sentry + Next.js: Complete Error Monitoring Guide (2026)

Most Next.js apps go to production with no idea what's actually failing for users. console.error doesn't reach you, Vercel logs have minified stack traces, and you find out about errors when a user reports them. Sentry fixes this — but the App Router setup has enough gotchas that a "quick install" turns into an afternoon.

Installation

npx @sentry/wizard@latest -i nextjs
Enter fullscreen mode Exit fullscreen mode

The wizard creates the three config files, wraps next.config.ts, generates instrumentation.ts and global-error.tsx, and sets up source map upload.

Three Config Files

Sentry initializes separately for each Next.js runtime:

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

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  integrations: [Sentry.replayIntegration({ maskAllText: true })],
  enabled: process.env.NODE_ENV === 'production',
})
Enter fullscreen mode Exit fullscreen mode
// sentry.server.config.ts
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 0.1,
  enabled: process.env.NODE_ENV === 'production',
})
Enter fullscreen mode Exit fullscreen mode
// sentry.edge.config.ts (for middleware)
Sentry.init({ dsn: process.env.SENTRY_DSN, tracesSampleRate: 0.1 })
Enter fullscreen mode Exit fullscreen mode

instrumentation.ts

// instrumentation.ts (at root, not inside app/)
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('../sentry.server.config')
  }
  if (process.env.NEXT_RUNTIME === 'edge') {
    await import('../sentry.edge.config')
  }
}
Enter fullscreen mode Exit fullscreen mode

Error Boundaries in App Router

error.tsx — route segment errors

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

export default function DashboardError({
  error, reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    // error.digest correlates client error with server log
    Sentry.captureException(error, { tags: { digest: error.digest } })
  }, [error])

  return (
    <div>
      <h2>Something went wrong</h2>
      <p>Reference: {error.digest}</p>
      <button onClick={reset}>Try again</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

global-error.tsx — root layout errors

error.tsx doesn't catch root layout errors. global-error.tsx does — and it must include <html> and <body>:

'use client'
export default function GlobalError({ error, reset }: { error: Error, reset: () => void }) {
  useEffect(() => { Sentry.captureException(error) }, [error])
  return (
    <html><body>
      <h1>Application Error</h1>
      <button onClick={reset}>Reload</button>
    </body></html>
  )
}
Enter fullscreen mode Exit fullscreen mode

Server Actions Instrumentation

Server Actions aren't automatically captured. Wrap them explicitly:

'use server'
import * as Sentry from '@sentry/nextjs'

export const createPost = Sentry.withServerActionInstrumentation(
  'createPost',
  { recordResponse: true },
  async (formData: FormData) => {
    await db.insert(posts).values({
      title: formData.get('title') as string,
    })
    return { success: true }
  }
)
Enter fullscreen mode Exit fullscreen mode

User Context

// After authentication
Sentry.setUser({ id: session.user.id, email: session.user.email })
Sentry.setTag('plan', 'pro')
Sentry.setContext('subscription', { seats: 25 })

// On logout
Sentry.setUser(null)
Enter fullscreen mode Exit fullscreen mode

Source Maps

Without source maps: at n (chunks/pages.js:1:4523) — useless.
With source maps: at DashboardPage (app/dashboard/page.tsx:47:12) — actionable.

# .env.local + Vercel env vars
SENTRY_AUTH_TOKEN=your-token  # Settings → API Keys in Sentry dashboard
SENTRY_ORG=your-org-slug
SENTRY_PROJECT=your-project-slug
Enter fullscreen mode Exit fullscreen mode

withSentryConfig in next.config.ts uploads source maps automatically during next build.

Sampling Strategy

tracesSampler: (ctx) => {
  if (ctx.parentSampled) return 1        // always trace if parent does
  const url = ctx.location?.href ?? ''
  if (url.includes('/checkout')) return 0.5
  if (url.includes('/api/webhooks')) return 1.0
  return 0.05                             // default: 5%
},
Enter fullscreen mode Exit fullscreen mode

Quick Reference

// Capture with context
Sentry.captureException(error, { tags: { action: 'checkout' } })

// User context
Sentry.setUser({ id: user.id, email: user.email })

// Custom span
await Sentry.startSpan({ name: 'db.query', op: 'db.query' }, () => db.select()...)

// Server Action wrapper
export const action = Sentry.withServerActionInstrumentation('name', {}, async () => {})

// error.tsx
useEffect(() => { Sentry.captureException(error) }, [error])
Enter fullscreen mode Exit fullscreen mode

Minimum viable setup: run the wizard, set enabled: process.env.NODE_ENV === 'production', add error.digest in error.tsx, set user context after auth. Everything else layers in as the app matures.


Full article at stacknotice.com/blog/sentry-nextjs-complete-guide-2026

Top comments (0)