fix(netlify): add runtime env fallback generation for server env reads#433
fix(netlify): add runtime env fallback generation for server env reads#433ViktorSvertoka merged 2 commits intodevelopfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📝 WalkthroughWalkthroughAdds a build-time generator that captures selected environment variables into a generated TypeScript module and updates server-side env resolution to optionally fall back to that generated module; Netlify build command runs the generator during pre-build. Changes
Sequence DiagramsequenceDiagram
participant Build as Build Process
participant Script as generate-env-runtime.mjs
participant EnvFile as .env.example
participant ProcEnv as process.env
participant Generated as runtime-env.generated.ts
participant Server as readServerEnv
Build->>Script: run generator
Script->>EnvFile: read keys
Script->>ProcEnv: lookup key values
ProcEnv-->>Script: return values (if any)
Script->>Generated: write RUNTIME_ENV (populated or {})
Generated-->>Build: file written
rect rgba(100,150,200,0.5)
Server->>ProcEnv: check process.env[key]
alt found non-blank
ProcEnv-->>Server: value
else not found
Server->>Generated: check RUNTIME_ENV[key] (allowlist only)
Generated-->>Server: trimmed non-blank or undefined
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
frontend/scripts/generate-env-runtime.mjs (1)
27-29: Normalize values before persisting.At
Line 28, whitespace-only env values are currently kept. Trim before checking emptiness so the generated map only contains usable values.Patch
for (const key of keys) { - const value = process.env[key]; - if (typeof value !== 'string' || value.length === 0) continue; - entries.push([key, value]); + const value = process.env[key]; + if (typeof value !== 'string') continue; + const normalized = value.trim(); + if (!normalized) continue; + entries.push([key, normalized]); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/scripts/generate-env-runtime.mjs` around lines 27 - 29, Trim environment variable values before checking emptiness and before storing them: when reading process.env[key] into value, call .trim() to normalize whitespace-only values, use the trimmed result for the typeof/length check, and push the trimmed string into entries so only non-empty, normalized values are persisted (refer to the local variables value and entries in generate-env-runtime.mjs).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/lib/env/server-env.ts`:
- Around line 37-43: The current readFromGeneratedRuntimeEnv(key)
unconditionally returns baked RUNTIME_ENV values which lets any env read fall
back to build-time values; change it to only allow fallback for an explicit
allowlist of safe keys (e.g., define a SAFE_GENERATED_KEYS Set containing
non-sensitive names) and return RUNTIME_ENV[key] trimmed only if key is in that
allowlist; otherwise return undefined so sensitive keys like
AUTH_SECRET/CSRF_SECRET do not silently use baked values. Ensure you update the
readFromGeneratedRuntimeEnv function and reference the SAFE_GENERATED_KEYS set
when deciding whether to use the generated fallback.
In `@frontend/scripts/generate-env-runtime.mjs`:
- Line 6: Remove the generated file from git tracking and ignore it: stop
tracking lib/env/runtime-env.generated.ts (use git rm --cached on that file) and
add lib/env/runtime-env.generated.ts to .gitignore so commits won't include
regenerated secrets; ensure the generate-env-runtime.mjs script remains
unchanged (Netlify will still regenerate the file during its build) and commit
the .gitignore and removal as a single change.
---
Nitpick comments:
In `@frontend/scripts/generate-env-runtime.mjs`:
- Around line 27-29: Trim environment variable values before checking emptiness
and before storing them: when reading process.env[key] into value, call .trim()
to normalize whitespace-only values, use the trimmed result for the
typeof/length check, and push the trimmed string into entries so only non-empty,
normalized values are persisted (refer to the local variables value and entries
in generate-env-runtime.mjs).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3914679c-79a2-4580-ae6f-b7bac3ead1bf
📒 Files selected for processing (4)
frontend/lib/env/runtime-env.generated.tsfrontend/lib/env/server-env.tsfrontend/scripts/generate-env-runtime.mjsnetlify.toml
frontend/lib/env/server-env.ts
Outdated
| return readFromGeneratedRuntimeEnv(key); | ||
| } | ||
|
|
||
| function readFromGeneratedRuntimeEnv(key: string): string | undefined { | ||
| const value = RUNTIME_ENV[key]; | ||
| return typeof value === 'string' && value.trim() ? value.trim() : undefined; | ||
| } No newline at end of file |
There was a problem hiding this comment.
Scope generated fallback to an explicit safe key set.
Line 37/Line 40-43 currently allow build-time fallback for every key. That can unintentionally satisfy sensitive reads (for example AUTH_SECRET / CSRF_SECRET) with baked values instead of failing fast on runtime misconfiguration.
Patch
+const GENERATED_FALLBACK_KEYS = new Set([
+ 'APP_ENV',
+ 'CONTEXT',
+ 'NETLIFY',
+ 'DATABASE_URL',
+ 'DATABASE_URL_LOCAL',
+]);
+
export function readServerEnv(key: string): string | undefined {
const fromProcess = process.env[key]?.trim();
if (fromProcess) return fromProcess;
const fromNetlify = readFromNetlifyEnv(key);
if (fromNetlify) return fromNetlify;
+ if (!GENERATED_FALLBACK_KEYS.has(key)) return undefined;
return readFromGeneratedRuntimeEnv(key);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return readFromGeneratedRuntimeEnv(key); | |
| } | |
| function readFromGeneratedRuntimeEnv(key: string): string | undefined { | |
| const value = RUNTIME_ENV[key]; | |
| return typeof value === 'string' && value.trim() ? value.trim() : undefined; | |
| } | |
| const GENERATED_FALLBACK_KEYS = new Set([ | |
| 'APP_ENV', | |
| 'CONTEXT', | |
| 'NETLIFY', | |
| 'DATABASE_URL', | |
| 'DATABASE_URL_LOCAL', | |
| ]); | |
| export function readServerEnv(key: string): string | undefined { | |
| const fromProcess = process.env[key]?.trim(); | |
| if (fromProcess) return fromProcess; | |
| const fromNetlify = readFromNetlifyEnv(key); | |
| if (fromNetlify) return fromNetlify; | |
| if (!GENERATED_FALLBACK_KEYS.has(key)) return undefined; | |
| return readFromGeneratedRuntimeEnv(key); | |
| } | |
| function readFromGeneratedRuntimeEnv(key: string): string | undefined { | |
| const value = RUNTIME_ENV[key]; | |
| return typeof value === 'string' && value.trim() ? value.trim() : undefined; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/lib/env/server-env.ts` around lines 37 - 43, The current
readFromGeneratedRuntimeEnv(key) unconditionally returns baked RUNTIME_ENV
values which lets any env read fall back to build-time values; change it to only
allow fallback for an explicit allowlist of safe keys (e.g., define a
SAFE_GENERATED_KEYS Set containing non-sensitive names) and return
RUNTIME_ENV[key] trimmed only if key is in that allowlist; otherwise return
undefined so sensitive keys like AUTH_SECRET/CSRF_SECRET do not silently use
baked values. Ensure you update the readFromGeneratedRuntimeEnv function and
reference the SAFE_GENERATED_KEYS set when deciding whether to use the generated
fallback.
|
|
||
| const root = process.cwd(); | ||
| const examplePath = resolve(root, '.env.example'); | ||
| const outputPath = resolve(root, 'lib/env/runtime-env.generated.ts'); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Tracked status:"
git ls-files --stage frontend/lib/env/runtime-env.generated.ts || true
echo
echo "Ignore rules mentioning runtime-env.generated.ts:"
fd -i .gitignore | xargs -I{} sh -c 'echo "--- {} ---"; rg -n "runtime-env\\.generated\\.ts" "{}" || true'Repository: DevLoversTeam/devlovers.net
Length of output: 229
🏁 Script executed:
cat -n frontend/scripts/generate-env-runtime.mjs | head -50Repository: DevLoversTeam/devlovers.net
Length of output: 1551
🏁 Script executed:
cat frontend/lib/env/runtime-env.generated.tsRepository: DevLoversTeam/devlovers.net
Length of output: 158
🏁 Script executed:
rg "runtime-env\.generated" --type ts --type js --type jsonRepository: DevLoversTeam/devlovers.net
Length of output: 156
🏁 Script executed:
find . -name "*.yml" -o -name "*.yaml" -o -name "*.json" -o -name "*.mjs" -o -name "Makefile" | grep -E "(github|gitlab|circleci|jenkins|\.github|\.gitlab|build|ci)" | head -20Repository: DevLoversTeam/devlovers.net
Length of output: 219
🏁 Script executed:
find . -path "./.git" -prune -o -type f \( -name "package.json" -o -name ".github/workflows/*.yml" \) -print | head -20Repository: DevLoversTeam/devlovers.net
Length of output: 116
🏁 Script executed:
cat frontend/package.json | grep -A 20 '"scripts"'Repository: DevLoversTeam/devlovers.net
Length of output: 1104
🏁 Script executed:
cat .github/workflows/security.ymlRepository: DevLoversTeam/devlovers.net
Length of output: 1363
🏁 Script executed:
rg "generate-env-runtime" --type ts --type js --type mjs --type jsonRepository: DevLoversTeam/devlovers.net
Length of output: 98
🏁 Script executed:
find .github/workflows -type f -name "*.yml" -exec cat {} \;Repository: DevLoversTeam/devlovers.net
Length of output: 3587
🏁 Script executed:
rg "generate-env-runtime" --type ts --type jsRepository: DevLoversTeam/devlovers.net
Length of output: 53
🏁 Script executed:
git log --oneline frontend/lib/env/runtime-env.generated.ts | head -5Repository: DevLoversTeam/devlovers.net
Length of output: 149
🏁 Script executed:
git show f6aabc8 --statRepository: DevLoversTeam/devlovers.net
Length of output: 50383
🏁 Script executed:
find . -name "next.config.*" -o -name "netlify.toml" | xargs cat 2>/dev/null || trueRepository: DevLoversTeam/devlovers.net
Length of output: 2862
Remove lib/env/runtime-env.generated.ts from git tracking and add it to .gitignore.
This file is generated by generate-env-runtime.mjs during Netlify builds and contains actual environment variable values. It's currently tracked in git without .gitignore protection, creating a clear path for developers to accidentally commit secrets if they run the generation script locally.
The file is already part of the Netlify build pipeline (netlify.toml calls the script before npm run build), so removing it from tracking won't break the build—Netlify will regenerate it with its own environment variables.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/scripts/generate-env-runtime.mjs` at line 6, Remove the generated
file from git tracking and ignore it: stop tracking
lib/env/runtime-env.generated.ts (use git rm --cached on that file) and add
lib/env/runtime-env.generated.ts to .gitignore so commits won't include
regenerated secrets; ensure the generate-env-runtime.mjs script remains
unchanged (Netlify will still regenerate the file during its build) and commit
the .gitignore and removal as a single change.
…b config - add build script to generate frontend/lib/env/runtime-env.generated.ts from .env.example allowlist - run generator before Next build in netlify.toml - extend readServerEnv fallback chain: process.env -> Netlify.env.get() -> generated runtime map - scope generated fallback to explicit keys only (APP_ENV/CONTEXT/NETLIFY/DATABASE_URL/DATABASE_URL_LOCAL/AUTH_SECRET/CSRF_SECRET) - keep generated map empty outside develop to reduce exposure in non-develop environments
There was a problem hiding this comment.
🧹 Nitpick comments (1)
frontend/lib/env/server-env.ts (1)
52-53: Fix inconsistent indentation.Lines 52-53 use a single space for indentation instead of matching the rest of the file.
Proposed fix
- if (!canUseGeneratedFallback(key)) return undefined; - return readFromGeneratedRuntimeEnv(key); + if (!canUseGeneratedFallback(key)) return undefined; + return readFromGeneratedRuntimeEnv(key);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/lib/env/server-env.ts` around lines 52 - 53, The two return statements that call canUseGeneratedFallback(key) and readFromGeneratedRuntimeEnv(key) use a single-space indent which is inconsistent with the file's indentation style; update the indentation of those lines to match the surrounding block (use the same number of spaces/tabs used elsewhere) where these calls appear (functions: canUseGeneratedFallback and readFromGeneratedRuntimeEnv, parameter: key) so the file's formatting is consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@frontend/lib/env/server-env.ts`:
- Around line 52-53: The two return statements that call
canUseGeneratedFallback(key) and readFromGeneratedRuntimeEnv(key) use a
single-space indent which is inconsistent with the file's indentation style;
update the indentation of those lines to match the surrounding block (use the
same number of spaces/tabs used elsewhere) where these calls appear (functions:
canUseGeneratedFallback and readFromGeneratedRuntimeEnv, parameter: key) so the
file's formatting is consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e58984dc-b1c7-4322-916e-b3e3ad8eb43b
📒 Files selected for processing (2)
frontend/lib/env/server-env.tsfrontend/scripts/generate-env-runtime.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/scripts/generate-env-runtime.mjs
What this PR changes
This PR hardens server env resolution for Netlify runtime, where
process.envmay be empty inside SSR/functions.Added
frontend/scripts/generate-env-runtime.mjsfrontend/lib/env/runtime-env.generated.tsat build time..env.exampleas an allowlist of keys.frontend/lib/env/runtime-env.generated.tsRUNTIME_ENV).Updated
frontend/lib/env/server-env.tsreadServerEnv()fallback chain is now:process.envNetlify.env.get()RUNTIME_ENV(generated at build)netlify.tomlnext build:node scripts/generate-env-runtime.mjsWhy
Runtime logs showed function cold starts with missing env values:
APP_ENV: <undefined>DATABASE_URL: falsewhich caused:
[db] no usable database configuration found (APP_ENV=undefined)502.This PR ensures required server env values are still available in runtime when direct env injection fails.
Safety / Scope
import 'server-only').NEXT_PUBLIC_*exposure added.Verification
generated runtime-env.generated.ts with <N> keys(N > 0)APP_ENV: <undefined>has_DATABASE_URL: false/enloads without502/api/auth/mereturns non-5xxSummary by CodeRabbit