Skip to content

fix(netlify): add runtime env fallback generation for server env reads#433

Merged
ViktorSvertoka merged 2 commits intodevelopfrom
sl/feat/db-optimization
Mar 29, 2026
Merged

fix(netlify): add runtime env fallback generation for server env reads#433
ViktorSvertoka merged 2 commits intodevelopfrom
sl/feat/db-optimization

Conversation

@LesiaUKR
Copy link
Copy Markdown
Collaborator

@LesiaUKR LesiaUKR commented Mar 29, 2026

What this PR changes

This PR hardens server env resolution for Netlify runtime, where process.env may be empty inside SSR/functions.

Added

  • frontend/scripts/generate-env-runtime.mjs
    • Generates frontend/lib/env/runtime-env.generated.ts at build time.
    • Uses .env.example as an allowlist of keys.
  • frontend/lib/env/runtime-env.generated.ts
    • Server-only generated fallback map (RUNTIME_ENV).

Updated

  • frontend/lib/env/server-env.ts
    • readServerEnv() fallback chain is now:
      1. process.env
      2. Netlify.env.get()
      3. RUNTIME_ENV (generated at build)
  • netlify.toml
    • Build command now runs generator before next build:
    • node scripts/generate-env-runtime.mjs

Why

Runtime logs showed function cold starts with missing env values:

  • APP_ENV: <undefined>
  • DATABASE_URL: false
    which caused:
  • [db] no usable database configuration found (APP_ENV=undefined)
  • site-level 502.

This PR ensures required server env values are still available in runtime when direct env injection fails.

Safety / Scope

  • Fallback is server-only (import 'server-only').
  • No NEXT_PUBLIC_* exposure added.
  • Existing fail-closed behavior is preserved if keys are truly absent.

Verification

  • Netlify build logs include:
    • generated runtime-env.generated.ts with <N> keys (N > 0)
  • Runtime logs no longer show:
    • APP_ENV: <undefined>
    • has_DATABASE_URL: false
  • /en loads without 502
  • /api/auth/me returns non-5xx

Summary by CodeRabbit

  • Chores
    • Build now pre-generates runtime environment values during the build, ensuring env data is prepared before the app starts.
  • New Behavior
    • Server-side environment lookup now uses a safer, restricted fallback for select environment keys and ignores blank or malformed values.
  • Developer Experience
    • Local development includes populated runtime env values; non-development builds emit an empty runtime env to avoid leaking local data.

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
devlovers-net Ignored Ignored Preview Mar 29, 2026 5:21pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Generated Environment Module
frontend/lib/env/runtime-env.generated.ts
New generated module exporting RUNTIME_ENV: Readonly<Record<string,string>> (may be {} when not in develop). Marked server-only.
Environment Resolution
frontend/lib/env/server-env.ts
readServerEnv(key) now checks process.env, Netlify runtime env, and for an allowlist of keys falls back to trimmed non-empty values from RUNTIME_ENV.
Build-time Generation Script
frontend/scripts/generate-env-runtime.mjs
New Node script that parses .env.example, collects matching process.env values, and writes runtime-env.generated.ts populated when APP_ENV is develop; otherwise writes empty {}.
Build Configuration
netlify.toml
Build command updated to run node scripts/generate-env-runtime.mjs after install and before npm run build.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • AM1007
  • ViktorSvertoka
  • liudmylasovetovs

Poem

🐰 I dug through .env with nimble feet,
Collected keys and made them neat,
Built a tiny module, tucked them in,
Now server lookups have a quiet grin.
Hop, build, deploy — a rabbit's little feat. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(netlify): add runtime env fallback generation for server env reads' clearly and specifically describes the main change: adding a runtime environment fallback generation mechanism for Netlify server environment reads. It uses a focused, concise phrasing that conveys the primary purpose of the changeset without being vague or overly broad.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sl/feat/db-optimization

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 10d2e45 and f6aabc8.

📒 Files selected for processing (4)
  • frontend/lib/env/runtime-env.generated.ts
  • frontend/lib/env/server-env.ts
  • frontend/scripts/generate-env-runtime.mjs
  • netlify.toml

Comment on lines +37 to +43
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 -50

Repository: DevLoversTeam/devlovers.net

Length of output: 1551


🏁 Script executed:

cat frontend/lib/env/runtime-env.generated.ts

Repository: DevLoversTeam/devlovers.net

Length of output: 158


🏁 Script executed:

rg "runtime-env\.generated" --type ts --type js --type json

Repository: 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 -20

Repository: 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 -20

Repository: 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.yml

Repository: DevLoversTeam/devlovers.net

Length of output: 1363


🏁 Script executed:

rg "generate-env-runtime" --type ts --type js --type mjs --type json

Repository: 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 js

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

git log --oneline frontend/lib/env/runtime-env.generated.ts | head -5

Repository: DevLoversTeam/devlovers.net

Length of output: 149


🏁 Script executed:

git show f6aabc8 --stat

Repository: DevLoversTeam/devlovers.net

Length of output: 50383


🏁 Script executed:

find . -name "next.config.*" -o -name "netlify.toml" | xargs cat 2>/dev/null || true

Repository: 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
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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

📥 Commits

Reviewing files that changed from the base of the PR and between f6aabc8 and f8d381a.

📒 Files selected for processing (2)
  • frontend/lib/env/server-env.ts
  • frontend/scripts/generate-env-runtime.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/scripts/generate-env-runtime.mjs

@ViktorSvertoka ViktorSvertoka merged commit de23891 into develop Mar 29, 2026
7 checks passed
@ViktorSvertoka ViktorSvertoka deleted the sl/feat/db-optimization branch March 29, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants