fix: use connection() for runtime env vars in Docker deployments#2051
fix: use connection() for runtime env vars in Docker deployments#2051nick-inkeep merged 1 commit intomainfrom
Conversation
…yments Use the stable Next.js `connection()` API to opt the root layout into dynamic rendering, ensuring runtimeConfig env vars are evaluated at request time instead of build time. This enables a single Docker image to be deployed across multiple environments with different env var values. Moved the runtimeConfig construction inside the component body so it executes per-request after `await connection()`, rather than at module load (build time). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 456e16d The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
📚 Docs Review: No documentation updates needed. This PR fixes the internal implementation for runtime environment variable handling in Docker deployments. The existing deployment docs already correctly describe the |
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
This is a clean, well-implemented fix. All three domain reviewers (standards, frontend, devops) returned with no findings.
Summary
The PR correctly uses the Next.js 15+ connection() API to opt the root layout into dynamic rendering, ensuring environment variables are evaluated at request time rather than build time. The key insight — moving the runtimeConfig object construction from module scope into the async component body — addresses the issue that the superseded PR #2050 missed.
What was reviewed:
- Code quality & standards: Clean implementation, no bugs or anti-patterns detected
- React/Next.js patterns: Proper async Server Component usage, correct
connection()API placement - DevOps/deployment: Changeset is present and appropriate, dual-deployment pattern (
PUBLIC_*||NEXT_PUBLIC_*) is preserved, Next.js version (16.1.6) fully supports the API
Strengths:
- Minimal, surgical change — one import, function signature change, and config relocation
- Well-documented PR description with clear rationale and alternative analysis
- Pre-existing issues (3 client components bypassing RuntimeConfigProvider) are explicitly flagged as out-of-scope for follow-up
- Build verification confirms all routes show
ƒ (Dynamic)as expected
✅ APPROVE
Summary: This is a clean fix that follows Next.js best practices for runtime environment variable evaluation. The implementation is correct, the changeset is appropriate, and the dual Vercel/Docker deployment compatibility is preserved. Ship it! 🚀
Reviewers (3)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-standards |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-frontend |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
pr-review-devops |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Total | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Summary
connection()API (v15+) to opt the root layout into dynamic rendering, ensuringruntimeConfigenvironment variables are evaluated at request time instead of build timeruntimeConfigconstruction inside the component body (cleanliness improvement, not strictly required for correctness)Root cause
The existing
RuntimeConfigProviderpattern is architecturally correct — it readsPUBLIC_*env vars server-side in the root layout and passes them to client components via React Context. ThesePUBLIC_*vars (non-NEXT_PUBLIC_) are NOT inlined by turbopack — they remain as liveprocess.envreferences in the compiled server-side JavaScript.The bug is not about where env vars are read, but when the component renders.
Without any dynamic rendering signal, Next.js statically prerenders the root layout during
next build. The component function runs at build time, readsprocess.env.PUBLIC_*(undefined/defaults in the build environment), and the resulting RSC payload — containing the serializedruntimeConfigobject — is cached as static output. At runtime in Docker, the cached RSC payload is served directly; the component function never re-runs, so the containers env vars are never read.await connection()tells Next.js to skip prerendering and re-render the layout per request. The sameprocess.env.PUBLIC_*references now execute at request time inside the running Docker container, picking up the correct env var values.Why
connection()?unstable_noStore)RuntimeConfigProviderpattern — no new dependencies neededWhy not alternatives?
export const dynamic = force-dynamicconnection()is more semantically precisenext-runtime-envlibraryRuntimeConfigProviderdoing the same thingsedscriptunstable_noStoreconnection()Dual deployment compatibility
The
PUBLIC_* || NEXT_PUBLIC_*fallback chain inruntimeConfigis preserved:NEXT_PUBLIC_*vars are inlined at build time (works as before)PUBLIC_*vars (without prefix) are read at runtime via dynamic renderingRelationship to PR #2050
Supersedes #2050. PR #2050 had the right idea (
await connection()) and would have worked. This PR additionally movesruntimeConfiginside the function body as a cleanliness improvement — keeping the env var reads co-located with the dynamic rendering signal — but the critical fix in both PRs isconnection()itself.Build verification
All routes correctly show Dynamic after the change (only
icon.svgremains static). Verified by inspecting compiled server-side JS thatprocess.env.PUBLIC_*references remain as live Node.js references (not inlined). No new build, lint, or format errors introduced.Known pre-existing issues (out of scope)
3 client components have direct
process.env.NEXT_PUBLIC_*references that bypassRuntimeConfigProvider. These are inlined at build time regardless of this fix:src/app/page.tsx:20—NEXT_PUBLIC_TENANT_ID(falls back to default — fine for Docker)src/components/sidebar-nav/app-sidebar.tsx:64—NEXT_PUBLIC_ENABLE_WORK_APPS(cloud-only feature flag, off by default)src/features/work-apps/slack/api/slack-api.ts:1—NEXT_PUBLIC_INKEEP_AGENTS_API_URL(gated behind work apps feature flag)These are low/no impact for Docker deployments and should be tracked independently if needed.
Test plan
process.env.PUBLIC_*are live references, not inlinedPUBLIC_INKEEP_AGENTS_API_URL=http://docker-test-api:9999,PUBLIC_SIGNOZ_URL=http://docker-test-signoz:7777,PUBLIC_NANGO_SERVER_URL=http://docker-test-nango:5555: response contains custom values, zero default values presentlocalhost:3002,localhost:3050,localhost:3080), zero custom values presentGenerated with Claude Code