Introduce __NEXT_INVARIANTS__ for process-level config values
Builds on the loadConfigFromFile/loadConfigForBuildWorker refactor to add __NEXT_INVARIANTS__ — a flat, frozen global object of pre-processed config values that never change after startup. This eliminates the pattern of threading values like trailingSlash through renderOpts → closures → function parameters when they're actually process-level constants.
__NEXT_INVARIANTS__ works identically in all contexts without the author needing to think about which one they're in. In client and server bundles, __NEXT_INVARIANTS__.trailingSlash is statically replaced with the literal value by defineEnv at compile time, enabling dead code elimination. In external (non-bundled) server code, the bare identifier resolves to the real frozen object on globalThis at runtime.
A Proxy sentinel is installed in node-environment-baseline.ts that throws if any property is accessed before config is resolved, catching ordering bugs early.
Initialization happens inside loadConfigFromFile and loadConfigForBuildWorker — no consumer needs to call it manually. It's idempotent (first call wins) so redundant calls are harmless.
The defineEnv entries are generated automatically by iterating Object.keys(__NEXT_INVARIANTS__). Adding a new invariant property to NextInvariants and initializeNextInvariants is sufficient — no manual defineEnv wiring needed.
All values must be JSON-serializable. The NextInvariants interface has an index signature constraint that produces a compile error if someone adds a non-serializable property like RegExp or Map.
The type declaration is in an internal-only src/next-invariants.d.ts included in the framework's tsconfig but not published to users via the next package types.
Initial properties: isDevServer, trailingSlash, experimentalOptimisticRouting.
As a first consumer, trailingSlash is migrated out of MetadataContext. resolve-url.ts reads __NEXT_INVARIANTS__.trailingSlash directly instead of receiving it through the closure chain, removing trailingSlash from MetadataContext, createMetadataContext, and the renderOpts dependency.
Tests:
Unit tests verify: Proxy sentinel throws on read/write before initialization, initialization produces correct frozen values, idempotency.
E2e tests with non-default config (trailingSlash: true, experimental.optimisticRouting: true) verify:
- Self-enforcing fixture parity — client component files in app/invariants/ must exactly match the keys of NextInvariants, so adding a new invariant without a test component fails
- SSR and browser hydration render correct values in client components (proves client bundle defineEnv replacement)
- SSR renders correct values in server components (proves server bundle defineEnv replacement)
- An external package using destructuring reads correct values from the runtime global (proves the globalThis path works for non-bundled code)
- Production bundle scan confirms no __NEXT_INVARIANTS__ string survives in any client or server JS file