--- name: santry-observability description: "Guide for implementing observability Next.js with Sentry for server actions using ZSA (Zod Server Actions)" --- ## Observability with Sentry ### Installation ```bash npm install @sentry/nextjs npx @sentry/wizard@latest -i nextjs ``` ### Configuration ```typescript // sentry.client.config.ts import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, environment: process.env.NODE_ENV, tracesSampleRate: 1.0, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, }); // sentry.server.config.ts import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, tracesSampleRate: 1.0, }); ``` ### Tracking Server Action Errors ```typescript "use server"; import * as Sentry from "@sentry/nextjs"; import { createServerAction } from "zsa"; import z from "zod"; export const createPaymentAction = createServerAction() .input(z.object({ amount: z.number().positive(), userId: z.string() })) .onError(async ({ err, input }) => { // Automatically capture errors to Sentry Sentry.captureException(err, { tags: { action: "createPayment", userId: input.userId, }, contexts: { action: { input: input, timestamp: new Date().toISOString(), }, }, }); }) .handler(async ({ input }) => { return await PaymentService.create(input); }); ``` ### Transaction Tracing ```typescript "use server"; import * as Sentry from "@sentry/nextjs"; import { createServerAction } from "zsa"; import z from "zod"; export const processOrderAction = createServerAction() .input(z.object({ orderId: z.string() })) .handler(async ({ input }) => { return await Sentry.startSpan( { name: "processOrderAction", op: "server.action", attributes: { orderId: input.orderId, }, }, async (span) => { // Step 1: Validate order const validationSpan = Sentry.startInactiveSpan({ name: "validateOrder", op: "validation", }); const order = await OrderService.validate(input.orderId); validationSpan?.end(); // Step 2: Process payment const paymentSpan = Sentry.startInactiveSpan({ name: "processPayment", op: "payment", }); const payment = await PaymentService.process(order); paymentSpan?.end(); // Step 3: Update inventory const inventorySpan = Sentry.startInactiveSpan({ name: "updateInventory", op: "inventory", }); await InventoryService.update(order.items); inventorySpan?.end(); span.setStatus({ code: 1, message: "success" }); return { orderId: order.id, status: "completed" }; } ); }); ``` ### User Context & Breadcrumbs ```typescript "use server"; import * as Sentry from "@sentry/nextjs"; import { authedProcedure } from "@/lib/procedures"; import z from "zod"; export const updateProfileAction = authedProcedure .createServerAction() .input(z.object({ name: z.string(), email: z.string().email() })) .onStart(async () => { Sentry.addBreadcrumb({ category: "action", message: "Profile update started", level: "info", }); }) .handler(async ({ input, ctx }) => { // Set user context for all events Sentry.setUser({ id: ctx.user.id, email: ctx.user.email, username: ctx.user.name, }); Sentry.addBreadcrumb({ category: "action", message: "Updating user profile", level: "info", data: { userId: ctx.user.id }, }); try { const result = await UserService.updateProfile(ctx.user.id, input); Sentry.addBreadcrumb({ category: "action", message: "Profile updated successfully", level: "info", }); return result; } catch (error) { Sentry.addBreadcrumb({ category: "action", message: "Profile update failed", level: "error", data: { error: (error as Error).message }, }); throw error; } }); ``` ### Custom Error Boundaries with Sentry ```typescript // components/error-boundary.tsx "use client"; import * as Sentry from "@sentry/nextjs"; import { useEffect } from "react"; export function ActionErrorBoundary({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { useEffect(() => { Sentry.captureException(error, { tags: { component: "ActionErrorBoundary", }, }); }, [error]); return (