|
1 | | -import { PUBLIC_ENV_KEY } from 'next-runtime-env' |
| 1 | +import { EnvScript } from 'next-runtime-env' |
2 | 2 |
|
3 | 3 | /** |
4 | | - * `NEXT_PUBLIC_*` values, captured once at module load (build time / server |
5 | | - * start) rather than read per-request - correct on the hosted deployment, |
6 | | - * where a build's env never changes between requests. Filter matches |
7 | | - * `next-runtime-env`'s own `getPublicEnv()` exactly. |
| 4 | + * `NEXT_PUBLIC_*` values, captured once when this module is first loaded - i.e. |
| 5 | + * at server start on the hosted deployment, where a build's env never changes |
| 6 | + * between requests. Filter matches `next-runtime-env`'s own `getPublicEnv()` |
| 7 | + * exactly. |
| 8 | + * |
| 9 | + * These are deliberately NOT the values Next inlines into the client bundle: |
| 10 | + * the image is built with placeholder `NEXT_PUBLIC_*` values and the real ones |
| 11 | + * are supplied to the container at start, so `window.__ENV` is the only source |
| 12 | + * of truth in the browser. |
8 | 13 | */ |
9 | 14 | const HOSTED_PUBLIC_ENV = Object.fromEntries( |
10 | 15 | Object.entries(process.env).filter(([key]) => /^NEXT_PUBLIC_/i.test(key)) |
11 | 16 | ) |
12 | 17 |
|
13 | 18 | /** |
14 | | - * Static, build-time equivalent of `next-runtime-env`'s `<PublicEnvScript>` |
15 | | - * for the hosted deployment. It populates `window[PUBLIC_ENV_KEY]` with the |
16 | | - * exact same shape `getEnv()` (`lib/core/config/env.ts`) reads client-side, |
17 | | - * but without `next-runtime-env`'s unconditional `unstable_noStore()` call - |
18 | | - * that call opts the entire app into dynamic rendering, which only pays off |
19 | | - * for self-hosted Docker images that re-inject env per deploy without a |
20 | | - * rebuild. On hosted, env is fixed per build, so this is safe to render |
21 | | - * statically alongside the marketing pages' `revalidate`. |
| 19 | + * Static equivalent of `next-runtime-env`'s `<PublicEnvScript>` for the hosted |
| 20 | + * deployment. It renders the library's own `<EnvScript>`, so the emitted markup |
| 21 | + * and its `beforeInteractive` loading strategy are identical to the self-hosted |
| 22 | + * path - only the env read differs. `<PublicEnvScript>` additionally calls |
| 23 | + * `unstable_noStore()`, which opts the entire app into dynamic rendering; that |
| 24 | + * only pays off for self-hosted Docker images that re-inject env per deploy |
| 25 | + * without a rebuild, so hosted reads the env once here and stays static. |
22 | 26 | * |
23 | | - * Escapes `<` in the serialized JSON so an env value containing `</script>` |
24 | | - * can't close this tag early and inject markup into every hosted page. |
| 27 | + * `beforeInteractive` is load-bearing, not an optimization. A plain `<script>` |
| 28 | + * rendered from the root layout lands at the end of `<head>`, after the ~40 |
| 29 | + * `<script async>` chunk tags Next emits at the top of the document; an `async` |
| 30 | + * script runs as soon as its fetch resolves, so on a warm cache a Next chunk |
| 31 | + * can execute - and hydration can begin - before the parser reaches the env |
| 32 | + * tag, leaving `window.__ENV` undefined for the first render. |
| 33 | + * `beforeInteractive` instead queues the script into `self.__next_s`, which |
| 34 | + * Next's `appBootstrap` drains to completion before calling `hydrate()`. |
25 | 35 | */ |
26 | 36 | export function PublicEnvScript() { |
27 | | - const serialized = JSON.stringify(HOSTED_PUBLIC_ENV).replace(/</g, '\\u003c') |
28 | | - return ( |
29 | | - <script |
30 | | - id='public-env' |
31 | | - dangerouslySetInnerHTML={{ |
32 | | - __html: `window['${PUBLIC_ENV_KEY}'] = ${serialized}`, |
33 | | - }} |
34 | | - /> |
35 | | - ) |
| 37 | + return <EnvScript env={HOSTED_PUBLIC_ENV} /> |
36 | 38 | } |
0 commit comments