From d2d5af68c716ccb41c4ab3f36609001ad030d368 Mon Sep 17 00:00:00 2001 From: Hans Kristian Flaatten Date: Sun, 12 Jul 2026 22:41:46 +0200 Subject: [PATCH] docs(frontend): sync auto-configuration + apm client reference to the completed config contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit auto-configuration.md (blocked on nais/naiserator#687 — the payload described here does not exist until that PR merges and deploys): - schemaVersion + environment fields, versioned-contract note - new 'Generated JSON file' section (nais.json sibling mount, initFromConfigUrl consumption, cache guidance) - NOTE: this page previously documented app.namespace, which the generator did not actually emit until #687 apm-client-api.md (true as of @nais/apm v0.5.0): - corrected the meta-tag claim: rendered by the app's own server (NaisMetaTags helpers), never injected by the platform - resolution chain updated: NAIS_FRONTEND_TELEMETRY_COLLECTOR_URL, tenant fallback (cluster/hostname), dev-mode vs loud-failure split, debug resolution table - new API docs: initFromConfigUrl, fromNaisConfig, NaisMetaTags SSR helpers; init options tenant + debug --- .../apm/reference/apm-client-api.md | 87 ++++++++++++++++--- .../frontend/reference/auto-configuration.md | 41 +++++++++ 2 files changed, 118 insertions(+), 10 deletions(-) diff --git a/docs/observability/apm/reference/apm-client-api.md b/docs/observability/apm/reference/apm-client-api.md index 2c0dc38a..e3f5dcab 100644 --- a/docs/observability/apm/reference/apm-client-api.md +++ b/docs/observability/apm/reference/apm-client-api.md @@ -48,6 +48,8 @@ each resolve independently (see [Configuration resolution](#configuration-resolu | `version` | `string` | App version / release. Used for grouping and release tagging; also set as Faro `release`. | | `environment` | `string` | Environment, e.g. `prod-gcp`. | | `telemetryUrl` | `string` | Collector URL. | +| `tenant` | `TenantProfile \| false` | Last-resort collector derivation profile. Defaults to the built-in nav profile; pass your own for another tenant, or `false` to disable derivation. | +| `debug` | `boolean` | Print the per-field configuration resolution table to the console. | | `beforeSend` | `(item) => item \| null` | Runs **before** the mandatory PII scrubber. Return `null` to drop the item. | | `ignoreErrors` | `Patterns` | Extra patterns appended to `DEFAULT_IGNORE_ERRORS`. | | `dangerouslyDisablePiiScrubbing` | `boolean` | Disables built-in PII scrubbing. See [Privacy](#privacy-pii-scrubbing). Default `false`. | @@ -89,17 +91,82 @@ resolves independently, highest priority first: - + ``` -3. **Build-time environment variables** — `NAIS_APP_NAME`, `NAIS_TEAM` (or - `NAIS_NAMESPACE`), `NAIS_CLUSTER_NAME`, and a version derived from - `NAIS_APP_IMAGE`'s tag (or `GITHUB_SHA`). These only work when your bundler - inlines `process.env.*` (webpack `DefinePlugin`, Vite `define`, Next.js - `env`). -4. **Collector fallback** — with no explicit or meta collector URL, well-known - Nais collectors are derived from the cluster name (`prod-*` and `dev-*`). -5. **Dev mode** — if no collector URL resolves at all (typically localhost), - nothing is sent; every signal is echoed to the console instead. + The tags are rendered by **your app's own server** — the platform does not + inject them into HTML. Don't write them by hand: use + [`` / `getNaisMetaTags()`](#naismetatags-ssr-helpers), which + read the pod's runtime environment for you. +3. **Environment variables** — `NAIS_APP_NAME`, `NAIS_TEAM` (or + `NAIS_NAMESPACE`), `NAIS_FRONTEND_TELEMETRY_COLLECTOR_URL`, and a version + derived from `GITHUB_SHA` or `NAIS_APP_IMAGE`'s tag. These are pod-runtime + values (SSR); in a browser bundle they only exist if your bundler inlines + `process.env.*` at build time — which is only ever correct for `version` + (CI knows `GITHUB_SHA`). **Never inline `NAIS_CLUSTER_NAME`**: one image + deploys to many clusters, so a baked-in value is wrong in at least one. +4. **Tenant fallback** — with no explicit/meta/env collector URL, the + collector is derived from the cluster name (when known) or — on non-local + hosts — from the page's hostname. Built in for the nav tenant; other + tenants pass their own `tenant` profile, and `tenant: false` disables + derivation. +5. **Dev mode / loud failure** — with no collector URL at all: on a genuinely + local host (`localhost`, `127.0.0.1`, `*.local`) nothing is sent and every + signal echoes to the console; on **any other host this is a + misconfiguration** and a specific `console.error` names what's missing and + how to fix it. `init()` never throws. + +Diagnose any resolution question with `init({ debug: true })` — it prints a +per-field table of which source won. + +## `initFromConfigUrl(url?, options?)` + +Fetch the platform's generated config (`nais.json`, mounted into your web root +via `spec.frontend.generatedConfig`) and initialize from it — the +zero-environment-config path for static SPAs: + +```ts +import { initFromConfigUrl } from '@nais/apm'; + +void initFromConfigUrl('/nais.json', { app: 'my-app', namespace: 'my-team' }); +``` + +Signals raised while the fetch is in flight (typically early errors) are +buffered and flushed after initialization — nothing is lost. If the fetch +fails, initialization proceeds with the standard resolution chain (loudly); +this function never throws. Explicit `options` win over fetched values. + +## `fromNaisConfig(config)` + +Map the platform's generated config payload +(`{ schemaVersion, telemetryCollectorURL, app: { name, namespace, version }, environment }`) +to `init()` options. For SSR servers that import the mounted module directly: + +```ts +const naisConfig = (await import('/app/nais.js')).default; +init({ ...fromNaisConfig(naisConfig), namespace: 'my-team' }); +``` + +Tolerates the previous payload generation (without `schemaVersion`/ +`namespace`/`environment`) and junk input (returns `{}`). + +## `NaisMetaTags` SSR helpers + +Render the five Nais meta tags from the pod's runtime environment so the +browser-side `init()` resolves everything with zero hand-written tags: + +```tsx +// Next.js: root layout (App Router) or in _document (Pages Router) +import { NaisMetaTags } from '@nais/apm/react'; + + + + +``` + +Non-React SSR: `getNaisMetaTags()` returns `{ name, content }[]`, and +`renderNaisMetaTags()` the same as an HTML string (attribute-escaped). All +three accept `overrides` (explicit values) and `naisConfig` (a generated-config +payload the server imported) — precedence: overrides → naisConfig → pod env. ## `captureException(error, options?)` diff --git a/docs/observability/frontend/reference/auto-configuration.md b/docs/observability/frontend/reference/auto-configuration.md index 95c1508a..9b46ec6b 100644 --- a/docs/observability/frontend/reference/auto-configuration.md +++ b/docs/observability/frontend/reference/auto-configuration.md @@ -31,36 +31,77 @@ The platform creates a JavaScript file at the specified `mountPath` with this st {% if tenant() == "nav" %} ```js export default { + schemaVersion: 1, telemetryCollectorURL: 'https://telemetry.nav.no/collect', app: { name: 'my-app', // from metadata.name in nais.yaml namespace: 'my-team', // from metadata.namespace in nais.yaml version: '2024-03-15-abc1234', // extracted from your container image tag }, + environment: 'prod-gcp', // the cluster this pod runs in }; ``` {% else %} ```js export default { + schemaVersion: 1, telemetryCollectorURL: '<>', app: { name: 'my-app', // from metadata.name in nais.yaml namespace: 'my-team', // from metadata.namespace in nais.yaml version: '2024-03-15-abc1234', // extracted from your container image tag }, + environment: 'prod-gcp', // the cluster this pod runs in }; ``` {% endif %} | Field | Source | | ---------------------- | ----------------------------------------- | +| `schemaVersion` | The payload contract generation (currently `1`); check it if you parse the file yourself | | `telemetryCollectorURL` | Set per cluster by the platform operator | | `app.name` | `metadata.name` from your `nais.yaml` | | `app.namespace` | `metadata.namespace` from your `nais.yaml` | | `app.version` | Tag from your container image | +| `environment` | The cluster your app runs in (e.g. `prod-gcp`) | + +The payload is a **versioned contract**: the shape only changes together with a +`schemaVersion` bump, and consumers (like `@nais/apm`) accept both the current +and the previous generation. The collector URL is set automatically based on which cluster your app runs in — you don't need separate config for dev and prod. +## Generated JSON file + +The same payload is also generated as `nais.json`, mounted **next to** the +JavaScript file (same directory as your `mountPath`). Use it when a `fetch()` +is more convenient than an ES module import — for example a static SPA that +serves its web root: + +```yaml +spec: + frontend: + generatedConfig: + mountPath: /usr/share/nginx/html/nais.js # nais.json is mounted alongside +``` + +{% if tenant() == "nav" %} +With [`@nais/apm`](../../apm/tutorials/track-frontend-errors.md) this is one call — +it fetches the file, applies it, and buffers any signals raised while the fetch +is in flight: + +```ts +import { initFromConfigUrl } from '@nais/apm'; + +void initFromConfigUrl('/nais.json', { app: 'my-app', namespace: 'my-team' }); +``` +{% endif %} + +!!! note "Cache the config file carefully" + The generated files are not content-hashed. If you serve them from your web + root, give them `Cache-Control: no-cache` (or a short TTL) so a new deploy's + values are picked up. + ## Environment variable The platform also sets this environment variable in your pod: