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: