Skip to content

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.tsonRequestError 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:

throw new Error("Sentry client smoke test");

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:

SENTRY_AUTH_TOKEN=<personal-token> SENTRY_ORG=fruitplug \
SENTRY_PROJECT=fruitplug-web pnpm build

Files of interest

  • apps/web/sentry.client.config.ts
  • apps/web/sentry.server.config.ts
  • apps/web/sentry.edge.config.ts
  • apps/web/app/instrumentation.ts
  • apps/web/app/error.tsx
  • apps/web/app/global-error.tsx
  • apps/web/next.config.ts (Sentry wrapper at the bottom)
  • wp-plugin/fruitplug-api/includes/Obs/SentryReporter.php