feat: auto-config — consume the platform channel, SSR helpers, loud misconfig failure - #18
Merged
Merged
Conversation
This was referenced Jul 12, 2026
Starefossen
force-pushed
the
feat/auto-config-phase-1
branch
from
July 12, 2026 15:38
5aa245c to
e4a06f7
Compare
Starefossen
marked this pull request as ready for review
July 12, 2026 16:39
Starefossen
enabled auto-merge
July 12, 2026 16:40
There was a problem hiding this comment.
Pull request overview
Adds a runtime config “auto-config” channel for @nais/apm (including naiserator-generated payload consumption), introduces SSR-friendly meta-tag helpers (plus a React component wrapper), and makes misconfiguration behavior explicitly loud on non-local hosts while preserving local dev-mode behavior.
Changes:
- Introduces
fromNaisConfig()andinitFromConfigUrl()to consume naiserator generatedConfig via module payload or fetchednais.json, including pre-init signal buffering. - Adds SSR meta-tag helpers (
getNaisMetaTags()/renderNaisMetaTags()) and<NaisMetaTags />in@nais/apm/react. - Expands config resolution logic (debug resolution table, tenant fallback behavior, local-host detection) and updates docs + test coverage accordingly.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/react/NaisMetaTags.tsx | New SSR React component that renders the resolved nais meta tags. |
| src/react/NaisMetaTags.test.tsx | Tests meta rendering behavior for env, overrides, and generatedConfig payload. |
| src/react/index.ts | Exports the new <NaisMetaTags /> component + props type. |
| src/naisConfigContract.test.ts | Adds a cross-repo contract test for naiserator’s generatedConfig payload shapes. |
| src/metaTags.ts | Adds server-side helpers to resolve/render the five nais meta tags. |
| src/metaTags.test.ts | Tests meta resolution precedence and HTML escaping behavior. |
| src/internal.ts | Adds pre-init buffering state and a runOrBuffer helper for capture calls. |
| src/initFromConfigUrl.test.ts | Integration test covering fetch-and-init + buffering during fetch. |
| src/initFromConfigUrl.failure.test.ts | Failure-path test ensuring fetch failure never blocks init and warns. |
| src/index.ts | Adds initFromConfigUrl() and exports new config/meta helpers. |
| src/config.ts | Extends config resolution (generatedConfig mapping, tenant profile, loud failure, debug table). |
| src/config.test.ts | Expands unit tests for new resolution sources, debug output, and helper functions. |
| src/config.prodhost.test.ts | Adds jsdom “real host” tests to pin loud-misconfig behavior. |
| src/config.navhost.test.ts | Adds jsdom nav-host tests for interim hostname-based collector derivation. |
| src/api.ts | Routes public API calls through buffering (runOrBuffer) during async init. |
| src/api.test.ts | Adds tests for buffered call ordering and context snapshot semantics. |
| README.md | Updates recommended init usage and documents per-architecture setup + loud failure behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+218
to
+222
| runOrBuffer((faro) => { | ||
| faro.api.pushMeasurement({ | ||
| type, | ||
| values, | ||
| ...(options.context ? { context: options.context } : {}), |
Comment on lines
235
to
+240
| export function pushEvent( | ||
| name: string, | ||
| attributes?: Record<string, string>, | ||
| domain?: string | ||
| ): void { | ||
| const faro = getFaroInstance(); | ||
| if (!faro) { | ||
| return; | ||
| } | ||
| faro.api.pushEvent(name, attributes, domain); | ||
| runOrBuffer((faro) => faro.api.pushEvent(name, attributes, domain)); |
Comment on lines
+195
to
+201
| } catch { | ||
| // eslint-disable-next-line no-console | ||
| console.warn( | ||
| `[@nais/apm] Fetching nais config from '${url}' failed (network error); ` + | ||
| 'continuing with standard config resolution.' | ||
| ); | ||
| } |
Starefossen
disabled auto-merge
July 12, 2026 17:51
Starefossen
enabled auto-merge
July 12, 2026 18:25
…ig failure Phase 1 of nais/grafana-apm-app#133 (ADR-0001): - fromNaisConfig() maps the naiserator generatedConfig payload (telemetryCollectorURL, app.{name,namespace,version}, environment, schemaVersion) to init options - initFromConfigUrl() fetches a served nais.json and initializes from it; signals raised while the fetch is in flight are buffered and flushed on init (never lost, capped at 100) - NAIS_FRONTEND_TELEMETRY_COLLECTOR_URL joins the telemetryUrl resolution chain (pod runtime, SSR) - Mode-aware failure: console-echo dev mode only on genuinely local hosts; on any other host a missing collector is a loud, specific console.error (never a silent no-op, never a throw) - init({ debug: true }) prints the per-field resolution table (which source won for each field)
Phase 2 of nais/grafana-apm-app#133: - getNaisMetaTags()/renderNaisMetaTags(): resolve the five nais meta tags from overrides -> generatedConfig payload -> pod runtime env; SSR servers author the tags with one call instead of by hand - <NaisMetaTags /> (@nais/apm/react): the same as a component for Next.js layouts/_document (React 19 hoists the metas into document.head) - Cross-repo contract test: naiserator's verbatim nais.js golden-file output (both the pre-#687 and schemaVersion 1 shapes) dynamically imported and fed through fromNaisConfig - README: canonical init({ app, namespace }); retract the 'platform injects meta tags' claim; correct the build-time env guidance (only version is safe to inline); document the loud-failure/dev-mode split, debug mode, and setup per serving architecture (SSR / pod-served SPA / CDN)
…file interface Phase 3 of nais/grafana-apm-app#133 (ADR-0001 decision 7): - TenantProfile: all domain-shaped knowledge (nav's collector URLs, cluster and hostname mappings) moves behind one swappable interface — tenants are physically isolated installations, so nothing tenant-specific may live in shared resolution code - navTenant (built-in, default): derives the collector from the cluster name (as before) or, on non-local hosts, from the page hostname (*.nav.no -> prod collector, *.dev.nav.no -> dev collector; dev checked first). Only the collector URL is derived, never environment - a hostname cannot tell prod-gcp from prod-fss and a fabricated cluster name is worse than none - tenant: false disables derivation; a custom profile serves other tenants - Explicitly interim: demoted once the platform-served well-known config URL ships (nais/grafana-apm-app#134 phase 3, #86) This is what makes bare init({ app, namespace }) in a CI-built static bundle send telemetry on nav domains with zero runtime wiring.
Starefossen
force-pushed
the
feat/auto-config-phase-1
branch
from
July 12, 2026 18:31
e4a06f7 to
98b6ac6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phases 1–2 of nais/grafana-apm-app#133, per ADR-0001 (#17).
Phase 1 — consume the platform channel (commit 1)
fromNaisConfig()— maps the naiseratorgeneratedConfigpayload to init options; tolerates today's incomplete payload (pre feat(frontend): complete the generatedConfig payload — namespace, environment, schemaVersion naiserator#687).initFromConfigUrl(url = '/nais.json', options?)— fetch-and-init for pod-served static SPAs. Never throws; falls through to standard resolution on failure. Pre-init buffering: capture calls during the fetch are buffered (capped at 100) and flushed on init, with context/PII sanitization snapshotted at call time (runOrBufferininternal.ts).NAIS_FRONTEND_TELEMETRY_COLLECTOR_URLjoins thetelemetryUrlchain.console.error, never silent, never a throw.init({ debug: true })— per-field resolution table.Phase 2 — SSR helpers + docs (commit 2)
getNaisMetaTags()/renderNaisMetaTags()— resolve the five nais meta tags from overrides → generatedConfig payload → pod runtime env; SSR apps author the tags with one call.<NaisMetaTags />(@nais/apm/react) — the same as a component for Next.js layouts/_document(React 19 hoists metas intodocument.head).naisConfigContract.test.ts): naiserator's verbatim golden-filenais.jsoutput — both the pre-#687 shape and the completedschemaVersion: 1shape — dynamically imported the way an SSR server would, and fed throughfromNaisConfig. Template drift on either side fails this test.init({ app: 'my-app', namespace: 'my-team' }); retracts the "injected by the nais platform" claim; corrects build-time env guidance (onlyversionviaGITHUB_SHAis safe to inline — neverNAIS_CLUSTER_NAME); adds Setup per serving architecture (SSR / pod-served SPA / CDN) and documents the loud-failure/dev-mode split.Tests
187 passing (45 new across both commits).
tsc --noEmitclean apart from the pre-existing@grafana/rrweb-typeserror on main.Companion
nais/naiserator#687 completes the platform payload (
app.namespace,environment,schemaVersion) and adds thenais.jsonvariant thatinitFromConfigUrlconsumes.Out of scope (later phases of #133)
Interim hostname fallback (tenant profile), release-inject build plugin.