fix(env): restore beforeInteractive on the hosted public env script (#6214)

The hosted `<PublicEnvScript>` rendered a plain `<script>`, which lands at
the end of `<head>` — after the ~40 `<script async>` chunk tags Next emits
at the top of the document. An async script runs as soon as its fetch
resolves, so on a warm cache a Next chunk could execute (and hydration
begin) before the parser reached the env tag, leaving `window.__ENV`
undefined for the first render.

That surfaced as "Something went wrong" on the workflow page, since
`getBaseUrl()` throws during the deploy modal's render, and as the socket
falling back to the page origin instead of NEXT_PUBLIC_SOCKET_URL.

Regressed in #5522, which replaced next-runtime-env's PublicEnvScript
(to avoid its unstable_noStore forcing dynamic rendering) with a static
equivalent that dropped the beforeInteractive strategy.

- render the library's own `<EnvScript>`, which defaults to
  beforeInteractive and does not call unstable_noStore — hosted and
  self-hosted now share one implementation and one loading strategy
- drop the hand-rolled serialization and `<` escaping; Next's
  beforeInteractive path already runs the payload through
  htmlEscapeJsonString, which escapes `& > < U+2028 U+2029`
- fall back to the browser origin in getBaseUrl() rather than throwing,
  so a missing injected env can never tear down a page through the error
  boundary; server-side callers still fail loudly
- read NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED via getEnv() in the SSO
  form, matching login/signup/auth-modal — `env.X` returns the build-time
  placeholder, not the runtime value
This commit is contained in:
Waleed
2026-08-03 12:03:33 -07:00
committed by GitHub
parent 0bc4fb4656
commit dd7951563e
6 changed files with 127 additions and 42 deletions
+11 -7
View File
@@ -26,14 +26,18 @@ function hasHttpProtocol(url: string): boolean {
function getBaseUrlImpl(): string {
const baseUrl = readEnv('NEXT_PUBLIC_APP_URL')?.trim()
if (!baseUrl) {
throw new Error(
'NEXT_PUBLIC_APP_URL must be configured for webhooks and callbacks to work correctly'
)
if (baseUrl) {
// Mirrors the real module: protocol-less values get https:// under isProd.
const protocol = envFlagsMock.isProd ? 'https://' : 'http://'
return hasHttpProtocol(baseUrl) ? baseUrl : `${protocol}${baseUrl}`
}
// Mirrors the real module: protocol-less values get https:// under isProd.
const protocol = envFlagsMock.isProd ? 'https://' : 'http://'
return hasHttpProtocol(baseUrl) ? baseUrl : `${protocol}${baseUrl}`
// Mirrors the real module: the browser falls back to its own origin, only
// server-side (no `window`) callers throw.
const browserOrigin = getBrowserOriginImpl()
if (browserOrigin) return browserOrigin
throw new Error(
'NEXT_PUBLIC_APP_URL must be configured for webhooks and callbacks to work correctly'
)
}
function getInternalApiBaseUrlImpl(): string {