Step 1: Run the Sentry Wizard
Create a new Sentry project at sentry.io, then configure your app automatically by running the Sentry wizard in your project root. You can find the personalized command in the Sentry getting-started guide during project creation.
bunx @sentry/wizard@latest -i nextjs --saas --org <org-name> --project <project-name>bunx @sentry/wizard@latest -i nextjs --saas --org <org-name> --project <project-name>Wizard selections:
- Runtime: Bun
- Route requests through your Next.js server: Yes (optional, recommended for privacy)
- Enable Tracing: Yes
- Session Replay: Yes
- Logs: Yes
- Example page: No
- Add Sentry MCP server: Yes
The wizard creates and updates the following TypeScript files:
next.config.ts- Next.js configurationsentry.server.config.ts- Server-side initialization (we'll delete this)sentry.edge.config.ts- Edge runtime initialization (we'll delete this)src/instrumentation-client.ts- Client-side initializationsrc/instrumentation.ts- Instrumentation hooksrc/app/global-error.tsx- Global error component
Step 2: Add environment variables
Add to your .env.development:
NEXT_PUBLIC_ENABLE_SENTRY="true"
NEXT_PUBLIC_SENTRY_DSN="https://your-dsn@sentry.io/your-project-id"
NEXT_PUBLIC_SENTRY_PROJECT="your-project-name"
NEXT_PUBLIC_SENTRY_ORG="your-org-name"
SENTRY_AUTH_TOKEN="your-auth-token"NEXT_PUBLIC_ENABLE_SENTRY="true"
NEXT_PUBLIC_SENTRY_DSN="https://your-dsn@sentry.io/your-project-id"
NEXT_PUBLIC_SENTRY_PROJECT="your-project-name"
NEXT_PUBLIC_SENTRY_ORG="your-org-name"
SENTRY_AUTH_TOKEN="your-auth-token"Then sync to Vercel with bun run env:push.
You can find your Sentry DSN, project name, and org name in your Sentry project settings or within the scaffolded files generated by the Sentry wizard. They're not secrets - they just tell Sentry where to send data. We move these to .env.development to enforce validation and also avoid committing them when working on open source repositories.
SENTRY_AUTH_TOKEN is a secret token added in .env.sentry-build-plugin by the Sentry wizard. You can delete the .env.sentry-build-plugin file after adding the token to .env.development. After that, you can also revert the changes made to .gitignore by removing the .env.sentry-build-plugin line.
Step 3: Create the Sentry config
Create the Sentry config with environment variable validation:
import { loadConfig } from "../common/load-config";
export const sentryConfig = loadConfig({
name: "Sentry",
flag: process.env.NEXT_PUBLIC_ENABLE_SENTRY,
server: {
// SENTRY_AUTH_TOKEN is picked up by the Sentry Build Plugin for source maps upload.
// Accessing this on the client will throw ServerConfigClientAccessError.
token: process.env.SENTRY_AUTH_TOKEN,
},
public: {
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
project: process.env.NEXT_PUBLIC_SENTRY_PROJECT,
org: process.env.NEXT_PUBLIC_SENTRY_ORG,
},
});import { loadConfig } from "../common/load-config";
export const sentryConfig = loadConfig({
name: "Sentry",
flag: process.env.NEXT_PUBLIC_ENABLE_SENTRY,
server: {
// SENTRY_AUTH_TOKEN is picked up by the Sentry Build Plugin for source maps upload.
// Accessing this on the client will throw ServerConfigClientAccessError.
token: process.env.SENTRY_AUTH_TOKEN,
},
public: {
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
project: process.env.NEXT_PUBLIC_SENTRY_PROJECT,
org: process.env.NEXT_PUBLIC_SENTRY_ORG,
},
});We use the loadConfig utility to validate the configuration and throw an error if any of the required environment variables are missing. The config separates server and public sections:
server.*values are only accessible on the server - accessing them on the client throws a helpful errorpublic.*values work everywhere (Next.js inlinesNEXT_PUBLIC_*vars at build time)
Step 4: Create the initialization helpers
Next, we'll refactor the wizard-generated files to use the new Sentry config. We further move the Sentry-related code into the src/lib/sentry directory to keep them organized.
Create the server initialization helper:
import * as Sentry from "@sentry/nextjs";
import { sentryConfig } from "./config";
export function initSentryServer() {
if (!sentryConfig.isEnabled) return;
Sentry.init({
dsn: sentryConfig.public.dsn,
tracesSampleRate: 1,
enableLogs: true,
sendDefaultPii: true,
integrations: [
Sentry.pinoIntegration({ log: { levels: ["info", "warn", "error"] } }),
],
});
}import * as Sentry from "@sentry/nextjs";
import { sentryConfig } from "./config";
export function initSentryServer() {
if (!sentryConfig.isEnabled) return;
Sentry.init({
dsn: sentryConfig.public.dsn,
tracesSampleRate: 1,
enableLogs: true,
sendDefaultPii: true,
integrations: [
Sentry.pinoIntegration({ log: { levels: ["info", "warn", "error"] } }),
],
});
}Create the edge initialization helper:
import * as Sentry from "@sentry/nextjs";
import { sentryConfig } from "./config";
export function initSentryEdge() {
if (!sentryConfig.isEnabled) return;
Sentry.init({
dsn: sentryConfig.public.dsn,
tracesSampleRate: 1,
enableLogs: true,
sendDefaultPii: true,
});
}import * as Sentry from "@sentry/nextjs";
import { sentryConfig } from "./config";
export function initSentryEdge() {
if (!sentryConfig.isEnabled) return;
Sentry.init({
dsn: sentryConfig.public.dsn,
tracesSampleRate: 1,
enableLogs: true,
sendDefaultPii: true,
});
}Note: The pino integration is not included for edge because pino uses Node.js modules (fs, events, worker_threads) that aren't available in the edge runtime.
Create the client initialization helper:
import * as Sentry from "@sentry/nextjs";
import { sentryConfig } from "./config";
export function initSentryClient() {
if (!sentryConfig.isEnabled) return;
Sentry.init({
dsn: sentryConfig.public.dsn,
integrations: [Sentry.replayIntegration()],
tracesSampleRate: 1,
enableLogs: true,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
sendDefaultPii: true,
});
}
export const onRouterTransitionStart = sentryConfig.isEnabled
? Sentry.captureRouterTransitionStart
: () => {};import * as Sentry from "@sentry/nextjs";
import { sentryConfig } from "./config";
export function initSentryClient() {
if (!sentryConfig.isEnabled) return;
Sentry.init({
dsn: sentryConfig.public.dsn,
integrations: [Sentry.replayIntegration()],
tracesSampleRate: 1,
enableLogs: true,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
sendDefaultPii: true,
});
}
export const onRouterTransitionStart = sentryConfig.isEnabled
? Sentry.captureRouterTransitionStart
: () => {};Step 5: Update the wizard-generated files
Delete the wizard-generated sentry.server.config.ts and sentry.edge.config.ts files from the project root. We'll import directly from our src/lib/sentry/ modules instead.
Replace the instrumentation file:
import * as Sentry from "@sentry/nextjs";
import { sentryConfig } from "./lib/sentry/config";
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { initSentryServer } = await import("./lib/sentry/server");
initSentryServer();
}
if (process.env.NEXT_RUNTIME === "edge") {
const { initSentryEdge } = await import("./lib/sentry/edge");
initSentryEdge();
}
}
export const onRequestError = sentryConfig.isEnabled
? Sentry.captureRequestError
: undefined;import * as Sentry from "@sentry/nextjs";
import { sentryConfig } from "./lib/sentry/config";
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { initSentryServer } = await import("./lib/sentry/server");
initSentryServer();
}
if (process.env.NEXT_RUNTIME === "edge") {
const { initSentryEdge } = await import("./lib/sentry/edge");
initSentryEdge();
}
}
export const onRequestError = sentryConfig.isEnabled
? Sentry.captureRequestError
: undefined;Replace the client instrumentation file:
// This file configures the initialization of Sentry on the client.
// The added config here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import { initSentryClient, onRouterTransitionStart } from "./lib/sentry/client";
initSentryClient();
export { onRouterTransitionStart };// This file configures the initialization of Sentry on the client.
// The added config here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import { initSentryClient, onRouterTransitionStart } from "./lib/sentry/client";
initSentryClient();
export { onRouterTransitionStart };Step 6: Update next.config.ts
The Sentry wizard updates next.config.ts with hardcoded org and project values. Replace them with environment variables:
// next.config.ts
import type { NextConfig } from "next";
import { withSentryConfig } from "@sentry/nextjs";
const nextConfig: NextConfig = {
/* config options here */
};
export default withSentryConfig(nextConfig, {
// For all available options, see:
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
org: process.env.NEXT_PUBLIC_SENTRY_ORG,
project: process.env.NEXT_PUBLIC_SENTRY_PROJECT,
// Only print logs for uploading source maps in CI
silent: !process.env.CI,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
// This can increase your server load as well as your hosting bill.
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
// side errors will fail.
tunnelRoute: "/monitoring",
webpack: {
// Automatically tree-shake Sentry logger statements to reduce bundle size
treeshake: {
removeDebugLogging: true,
},
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: true,
},
});// next.config.ts
import type { NextConfig } from "next";
import { withSentryConfig } from "@sentry/nextjs";
const nextConfig: NextConfig = {
/* config options here */
};
export default withSentryConfig(nextConfig, {
// For all available options, see:
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
org: process.env.NEXT_PUBLIC_SENTRY_ORG,
project: process.env.NEXT_PUBLIC_SENTRY_PROJECT,
// Only print logs for uploading source maps in CI
silent: !process.env.CI,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
// This can increase your server load as well as your hosting bill.
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
// side errors will fail.
tunnelRoute: "/monitoring",
webpack: {
// Automatically tree-shake Sentry logger statements to reduce bundle size
treeshake: {
removeDebugLogging: true,
},
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: true,
},
});Database query monitoring
When tracing is enabled (tracesSampleRate > 0), Sentry automatically instruments database queries using postgresIntegration. This is included by default - no additional configuration needed.
What you get automatically:
- All
pg(node-postgres) queries captured as spans - Query timing and slow query detection
- Database performance visible in Sentry's Performance tab
- Works with Drizzle ORM since it uses
node-postgresunder the hood
This uses OpenTelemetry instrumentation (@opentelemetry/instrumentation-pg) to hook into the pg library. Supports pg versions 8.x.
To disable or customize (if needed):
Sentry.init({
dsn: "...",
tracesSampleRate: 1,
integrations: (defaults) => {
// Remove postgres integration
return defaults.filter((i) => i.name !== "Postgres");
},
});Sentry.init({
dsn: "...",
tracesSampleRate: 1,
integrations: (defaults) => {
// Remove postgres integration
return defaults.filter((i) => i.name !== "Postgres");
},
});Step 7: Create Cursor rules for Sentry
Create .cursor/rules/sentry.mdc to help AI coding agents use Sentry APIs correctly:
---
description: Sentry error monitoring and tracking
alwaysApply: false
---
These examples should be used as guidance when configuring Sentry functionality within a project.
# Exception Catching
Use `Sentry.captureException(error)` to capture an exception and log the error in Sentry.
Use this in try catch blocks or areas where exceptions are expected
# Tracing Examples
Spans should be created for meaningful actions within an applications like button clicks, API calls, and function calls
Use the `Sentry.startSpan` function to create a span
Child spans can exist within a parent span
## Custom Span instrumentation in component actions
The `name` and `op` properties should be meaninful for the activities in the call.
Attach attributes based on relevant information and metrics from the request
```tsx
function TestComponent() {
const handleTestButtonClick = () => {
// Create a transaction/span to measure performance
Sentry.startSpan(
{
op: "ui.click",
name: "Test Button Click",
},
(span) => {
const value = "some config";
const metric = "some metric";
// Metrics can be added to the span
span.setAttribute("config", value);
span.setAttribute("metric", metric);
doSomething();
},
);
};
return (
<button type="button" onClick={handleTestButtonClick}>
Test Sentry
</button>
);
}
```
## Custom span instrumentation in API calls
The `name` and `op` properties should be meaninful for the activities in the call.
Attach attributes based on relevant information and metrics from the request
```typescript
async function fetchUserData(userId) {
return Sentry.startSpan(
{
op: "http.client",
name: `GET /api/users/${userId}`,
},
async () => {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
return data;
},
);
}
```
# Logs
Where logs are used, ensure Sentry is imported using `import * as Sentry from "@sentry/nextjs"`
Enable logging in Sentry using `Sentry.init({ enableLogs: true })`
Reference the logger using `const { logger } = Sentry`
Sentry offers a consoleLoggingIntegration that can be used to log specific console error types automatically without instrumenting the individual logger calls
## Configuration
In NextJS the client side Sentry initialization is in `instrumentation-client.(js|ts)`, the server initialization is in `sentry.server.config.ts` and the edge initialization is in `sentry.edge.config.ts`
Initialization does not need to be repeated in other files, it only needs to happen the files mentioned above. You should use `import * as Sentry from "@sentry/nextjs"` to reference Sentry functionality
### Baseline
```typescript
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://your-dsn@sentry.io/your-project-id",
enableLogs: true,
});
```
### Logger Integration
```typescript
Sentry.init({
dsn: "https://your-dsn@sentry.io/your-project-id",
integrations: [
// send console.log, console.warn, and console.error calls as logs to Sentry
Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }),
],
});
```
## Logger Examples
`logger.fmt` is a template literal function that should be used to bring variables into the structured logs.
```typescript
logger.trace("Starting database connection", { database: "users" });
logger.debug(logger.fmt`Cache miss for user: ${userId}`);
logger.info("Updated profile", { profileId: 345 });
logger.warn("Rate limit reached for endpoint", {
endpoint: "/api/results/",
isEnterprise: false,
});
logger.error("Failed to process payment", {
orderId: "order_123",
amount: 99.99,
});
logger.fatal("Database connection pool exhausted", {
database: "users",
activeConnections: 100,
});
```---
description: Sentry error monitoring and tracking
alwaysApply: false
---
These examples should be used as guidance when configuring Sentry functionality within a project.
# Exception Catching
Use `Sentry.captureException(error)` to capture an exception and log the error in Sentry.
Use this in try catch blocks or areas where exceptions are expected
# Tracing Examples
Spans should be created for meaningful actions within an applications like button clicks, API calls, and function calls
Use the `Sentry.startSpan` function to create a span
Child spans can exist within a parent span
## Custom Span instrumentation in component actions
The `name` and `op` properties should be meaninful for the activities in the call.
Attach attributes based on relevant information and metrics from the request
```tsx
function TestComponent() {
const handleTestButtonClick = () => {
// Create a transaction/span to measure performance
Sentry.startSpan(
{
op: "ui.click",
name: "Test Button Click",
},
(span) => {
const value = "some config";
const metric = "some metric";
// Metrics can be added to the span
span.setAttribute("config", value);
span.setAttribute("metric", metric);
doSomething();
},
);
};
return (
<button type="button" onClick={handleTestButtonClick}>
Test Sentry
</button>
);
}
```
## Custom span instrumentation in API calls
The `name` and `op` properties should be meaninful for the activities in the call.
Attach attributes based on relevant information and metrics from the request
```typescript
async function fetchUserData(userId) {
return Sentry.startSpan(
{
op: "http.client",
name: `GET /api/users/${userId}`,
},
async () => {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
return data;
},
);
}
```
# Logs
Where logs are used, ensure Sentry is imported using `import * as Sentry from "@sentry/nextjs"`
Enable logging in Sentry using `Sentry.init({ enableLogs: true })`
Reference the logger using `const { logger } = Sentry`
Sentry offers a consoleLoggingIntegration that can be used to log specific console error types automatically without instrumenting the individual logger calls
## Configuration
In NextJS the client side Sentry initialization is in `instrumentation-client.(js|ts)`, the server initialization is in `sentry.server.config.ts` and the edge initialization is in `sentry.edge.config.ts`
Initialization does not need to be repeated in other files, it only needs to happen the files mentioned above. You should use `import * as Sentry from "@sentry/nextjs"` to reference Sentry functionality
### Baseline
```typescript
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://your-dsn@sentry.io/your-project-id",
enableLogs: true,
});
```
### Logger Integration
```typescript
Sentry.init({
dsn: "https://your-dsn@sentry.io/your-project-id",
integrations: [
// send console.log, console.warn, and console.error calls as logs to Sentry
Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }),
],
});
```
## Logger Examples
`logger.fmt` is a template literal function that should be used to bring variables into the structured logs.
```typescript
logger.trace("Starting database connection", { database: "users" });
logger.debug(logger.fmt`Cache miss for user: ${userId}`);
logger.info("Updated profile", { profileId: 345 });
logger.warn("Rate limit reached for endpoint", {
endpoint: "/api/results/",
isEnterprise: false,
});
logger.error("Failed to process payment", {
orderId: "order_123",
amount: 99.99,
});
logger.fatal("Database connection pool exhausted", {
database: "users",
activeConnections: 100,
});
```File Structure
After setup, you'll have these Sentry-related files:
src/
instrumentation.ts # Next.js instrumentation hook - calls initSentryServer() or initSentryEdge()
instrumentation-client.ts # Client instrumentation - calls initSentryClient()
lib/sentry/
config.ts # Validates NEXT_PUBLIC_SENTRY_DSN, NEXT_PUBLIC_SENTRY_PROJECT, NEXT_PUBLIC_SENTRY_ORG
server.ts # initSentryServer() with pino integration
edge.ts # initSentryEdge() without pino (edge doesn't support Node.js modules)
client.ts # initSentryClient() with replay integration
.cursor/rules/
sentry.mdc # AI agent guidelines for Sentrysrc/
instrumentation.ts # Next.js instrumentation hook - calls initSentryServer() or initSentryEdge()
instrumentation-client.ts # Client instrumentation - calls initSentryClient()
lib/sentry/
config.ts # Validates NEXT_PUBLIC_SENTRY_DSN, NEXT_PUBLIC_SENTRY_PROJECT, NEXT_PUBLIC_SENTRY_ORG
server.ts # initSentryServer() with pino integration
edge.ts # initSentryEdge() without pino (edge doesn't support Node.js modules)
client.ts # initSentryClient() with replay integration
.cursor/rules/
sentry.mdc # AI agent guidelines for Sentry