React + Vite quickstart with @nais/apm ΒΆ

Instrument a React single-page app (SPA) built with Vite using @nais/apm, the Nais browser telemetry SDK, and see your errors as issues in Nais APM. This is the fast path for a client-rendered React app: init at the entry point, a React error boundary, and route tracking for React Router.

Status: pre-release

@nais/apm is pre-1.0. Pin an exact version and read the CHANGELOG before upgrading. This guide targets 0.4.0.

Migrating off Sentry?

If your app calls Sentry.init today, follow Migrate from Sentry to @nais/apm instead β€” it maps every @sentry/react call site to its @nais/apm equivalent and lists what's different. On Next.js instead? See the Next.js quickstart.

Prerequisites ΒΆ

  • A React SPA built with Vite, deployed on Nais.
  • The @nais package registry configured in your .npmrc. This is a one-time setup β€” follow step 1 of Track frontend errors.

Install ΒΆ

sh

The React helpers used below β€” ApmErrorBoundary, enableApmReactRouterV6, ApmRoutes β€” live in the @nais/apm/react entry point.

1. Initialize at the entry point ΒΆ

Call init() once, as early as possible, in your app's entry module β€” before you render. On Nais, everything resolves automatically except the team, which you pass as namespace:

tsx

init() is idempotent, so React Strict Mode's double render won't re-initialize. App name, version, environment, and the collector URL resolve automatically from Nais meta tags or NAIS_* build env. On localhost, where no collector resolves, it sends nothing and echoes every signal to the console.

`namespace` is your team

namespace is the Nais team that owns the app (its Kubernetes namespace). Nais APM attributes all telemetry by team, so it's effectively required. Pass it explicitly as shown, or let it resolve from a <meta name="nais-team" content="my-team"> tag or the NAIS_TEAM env β€” then you can drop the option entirely.

Inlining `NAIS_*` env with Vite

The env-var resolution path only works if Vite inlines the values at build time. Expose them through Vite's define, e.g. define: { 'process.env.NAIS_TEAM': JSON.stringify(process.env.NAIS_TEAM) }. Passing namespace explicitly (or via a meta tag) avoids needing this.

2. Add an error boundary ΒΆ

Wrap your app in ApmErrorBoundary. It reports each caught render error exactly once through captureException β€” picking up the SDK's fingerprint and context β€” and renders a fallback:

tsx

The fallback can also be a render prop (error, resetError) => node, and withApmErrorBoundary(Component, props?) gives you the HOC form.

3. Track route changes (React Router) ΒΆ

For client-side navigation, report route changes so pageviews and errors carry the right URL. @nais/apm re-exports faro-react's route tracking as ApmRoutes. Call enableApmReactRouterV6 once after init(), passing your own react-router-dom exports (the SDK never imports react-router itself, which keeps its version out of its dependency tree), then render <ApmRoutes> where you'd render <Routes>:

tsx
tsx

React Router v6 only

Route tracking currently wires up React Router v6. Many navikt apps are on v7 β€” the wiring shown here targets v6's API, so on v7 confirm these exports still resolve, or track it against the @nais/apm CHANGELOG. v5 and the data-router variants are not wired yet. The error boundary and manual captures work regardless of router version.

4. Enable tracing (optional) ΒΆ

To connect browser spans to your backend traces in Tempo, turn on tracing. @grafana/faro-web-tracing is lazily loaded, so it stays out of your bundle unless you enable it:

ts

Trace headers are propagated to your own origin and *.nav.no backends by default (a non-overridable security floor). Add more origins with tracing: { propagateExtraOrigins: ['https://api.partner.example'] }. Your backend still has to allow the traceparent CORS header and export spans to Tempo β€” see frontend-to-backend trace propagation.

Ship readable stack traces ΒΆ

Stack traces resolve server-side from sourcemaps on the CDN β€” nothing to upload (how it works). You just have to emit them. For Vite:

js

A Vite SPA served from the CDN is the ideal case β€” the .map files sit next to the bundle. See Sourcemap deobfuscation β†’ Requirements.

Don't send personal data ΒΆ

Telemetry lands in a shared Loki instance every team can read, so identities must never reach it. setUser takes an opaque/hashed id only β€” emails, idents, and fΓΈdselsnummer are dropped in code β€” and a mandatory PII scrubber runs over every outgoing signal. See Privacy: PII scrubbing for the full rules.

See your errors as issues ΒΆ

Deploy, then trigger an error. Open your service in Nais APM and go to the Issues tab. Your exception shows up as an issue, grouped with other occurrences, with its (deobfuscated) stack trace and impact. Filter the source to Browser to isolate frontend issues.

From there you can triage it or create an alert.