Observability¶
Fruit Plug uses Sentry for error tracking and light performance tracing across both halves of the stack:
- PWA —
@sentry/nextjs(client + server + edge runtimes) - WordPress plugin — a tiny dependency-free shim at
wp-plugin/fruitplug-api/includes/Obs/SentryReporter.php
Both are env-gated. With no DSN set, nothing initialises, nothing is sent, and bundle/runtime overhead is essentially zero.
PWA — environment variables¶
Add these to .env.local (dev) or /etc/fruitplug/web.env (prod):
# Server-side DSN — Node + Edge runtimes, never inlined into the browser.
SENTRY_DSN=https://<public-key>@<org>.ingest.sentry.io/<project-id>
# Browser DSN — must use the NEXT_PUBLIC_ prefix so Next inlines it.
# Usually the same value as SENTRY_DSN.
NEXT_PUBLIC_SENTRY_DSN=https://<public-key>@<org>.ingest.sentry.io/<project-id>
# Optional — enables source-map upload during `next build`. When unset, the
# Sentry webpack plugin is skipped entirely (no warnings, no upload).
SENTRY_AUTH_TOKEN=
Then start the dev server: pnpm dev. That's it — no flags, no manual init.
What's captured¶
| Surface | Captures | Sample rate |
|---|---|---|
Browser (sentry.client.config.ts) |
Unhandled errors, route-error boundary throws, performance traces | tracesSampleRate: 0.1 |
| Browser session replay | Only on errored sessions | replaysOnErrorSampleRate: 0.1 |
Node server (sentry.server.config.ts) |
Uncaught exceptions in RSCs, route handlers, server actions | tracesSampleRate: 0.1 |
Edge runtime (sentry.edge.config.ts) |
Middleware + edge route handler errors | tracesSampleRate: 0.1 |
app/instrumentation.ts → onRequestError |
Errors that escape any server-rendered request | always |
app/error.tsx |
Anything thrown while rendering a route segment | always |
app/global-error.tsx |
Crashes in RootLayout itself |
always |
Session replays are deliberately set to 0% baseline — Fruit Plug is a commerce site with addresses and payment forms in the DOM. We only record sessions that already errored, where the upside outweighs the privacy cost.
Adding contextual tags¶
Anywhere in the PWA you can attach extra context to a captured event:
import * as Sentry from "@sentry/nextjs";
Sentry.withScope((scope) => {
scope.setTag("feature", "build-your-box");
scope.setContext("box", { weightKg: 4.5, items: 7 });
Sentry.captureException(err);
});
Tags become filterable facets in the Sentry UI; contexts show as expandable JSON on the event detail page.
For request-scoped tags (e.g. user id), prefer Sentry.setUser({ id })
inside a server action or route handler.
Reproducing an error¶
The fastest smoke test is a temporary throw in any route handler:
// apps/web/app/api/healthz/route.ts (DO NOT COMMIT)
export function GET() {
throw new Error("Sentry server smoke test");
}
Hit /api/healthz and watch for the event in Sentry → Issues. Roll the
change back.
For the client, paste this into the browser console on any page:
The unhandled error gets caught by the SDK's global handler and forwarded
as long as NEXT_PUBLIC_SENTRY_DSN is set.
WordPress plugin¶
The plugin reporter is gated on a wp-config constant — see the A2 host runbook for the one-line wp-config change. Locally there is nothing to wire up; just don't define the constant.
Source-map upload (production)¶
next.config.ts is wrapped with withSentryConfig only when
SENTRY_AUTH_TOKEN is present at build time. CI exports the token; local
builds don't, so contributors never see the upload step run. To upload maps
locally for debugging:
Files of interest¶
apps/web/sentry.client.config.tsapps/web/sentry.server.config.tsapps/web/sentry.edge.config.tsapps/web/app/instrumentation.tsapps/web/app/error.tsxapps/web/app/global-error.tsxapps/web/next.config.ts(Sentry wrapper at the bottom)wp-plugin/fruitplug-api/includes/Obs/SentryReporter.php