Repository navigation
Release Notes 4.1.0
Released: 2026-11-04
This release fixes edge cases in envParsers, makeEnvPublic, and EnvScript. It also lets CommonJS consumers such as a compiled next.config.ts load the package, and the npm tarball now contains only the build output. Behind that, the toolchain moves to pnpm 12, TypeScript 7, Vitest 5, and Next 16.4. The example apps are rebuilt for Next.js 16, and the docs were rewritten to match the current code.
🌟 Highlights
🐛 nextScriptProps no longer drops beforeInteractive
Before this release, nextScriptProps defaulted to { strategy: 'beforeInteractive' }, so passing any props replaced the whole object. Passing only an id was enough to lose the strategy. Next then fell back to afterInteractive, and client components hydrated before window.__ENV existed, so they read undefined. EnvScript now sets strategy="beforeInteractive" on <Script> and spreads nextScriptProps over it. <EnvScript nextScriptProps={{ id: 'runtime-env' }} /> keeps the correct strategy, and a regression test covers this case.
📦 require() support and a smaller tarball
The exports map had only an import condition, so require('next-dynenv') threw ERR_PACKAGE_PATH_NOT_EXPORTED. Next compiles next.config.ts to CommonJS, so importing the library there failed. A default condition now lets require(esm) load it on Node 20.19+ and 22.12+. A files: ["build"] allowlist and a new tsconfig.build.json keep spec files, test-setup.js, and inline source maps out of the tarball, which drops it from 59 files to 45. The package also declares "sideEffects": false, so bundlers can drop the server components from client bundles that only import env().
🐛 Parser and makeEnvPublic fixes
envParsers.numberreturns the default for empty or whitespace-only values. It used to return0becauseNumber('')is0. It now throws for non-finite values such asInfinity.envParsers.booleantrims surrounding whitespace before matching, so' true 'istrue. Any other defined value, including an empty string, is stillfalse. The default only applies when the variable is unset.makeEnvPublicnow prefixes variables that are set to an empty string. It used to skip them and warn that they were missing.
🐛 Prerender bailouts are no longer swallowed
When EnvScript read the nonce from a header, it caught any error containing Dynamic server usage. That error is how Next.js marks a route as dynamic during prerendering, so it now propagates. The only error that still means "render without a nonce" is outside a request scope (CLI scripts, tests).
♻️ Example apps rebuilt for Next.js 16
examples/with-app-router-context and examples/with-app-router-script are now small runnable demos. Each has a client component, a server component, and a route handler.
- The script example moves its CSP nonce from
middleware.tstoproxy.ts, following the Next CSP guide. - Server pages call
connection(), andmakeEnvPublicruns ininstrumentation.ts, so it also works in standalone builds. next.config.jsis replaced bynext.config.ts. Removed experimental flags andnext lintare gone.- The
.env.examplefiles now match the variables the apps read.
📝 Documentation
- Every guide, API page, example page, and the README was checked against
srcand rewritten where it had drifted. Fixed problems include:- samples that imported the unexported
getPublicEnv makeEnvPublicdocumented as a build-timenext.configcall- wrong parser semantics
env()listed as never throwingafterInteractive/onLoadexamples passed throughnextScriptProps
- samples that imported the unexported
- New pages:
docs/api/server-only.md,docs/api/make-env-public.md, anddocs/guide/troubleshooting.md. - The security guide and JSDoc now explain that client components also render on the server. There,
env()andserverOnly()return the real value of a non-public key, which would end up in the HTML. - JSDoc for
makeEnvPublicsays to call it fromregister()ininstrumentation.ts. Standalone builds (output: 'standalone') never runnext.configat runtime. - JSDoc for
disableNextScriptexplains when to use it: code ininstrumentation-client.ts(for example a Sentry init) runs beforebeforeInteractivescripts. When this option is on, passnonceexplicitly under a CSP. - The old note that
NonceConfigwas blocked by Next.js PR #58129 is gone.nonce={{ headerKey: 'x-nonce' }}is documented with a header set inproxy.ts. - The
env()browser error now suggestsmakeEnvPublicininstrumentation.tsinstead ofnext.config.js. - The VitePress theme uses the SilkCircuit Neon and Dawn palettes and code themes, adds a hero mark, defaults to dark mode, and fixes broken favicon and og-image links.
CHANGELOG.mdnow points to GitHub Releases.
⬆️ Toolchain and Dependencies
- The workspace moves to pnpm 12.16. pnpm 11 stopped reading the
pnpmfield inpackage.json, so the oldglobandbrace-expansionoverrides had been silently ignored. Overrides now live inpnpm-workspace.yaml. - Catalog bumps: Next 16.4.3, React 19.3.1, TypeScript 7.0.2, Vitest 5.0.5, jsdom 30,
@types/node26, Biome 2.6.4, Prettier 3.9.10. semantic-releaseandaudit-ciare removed. Their dependency trees carried most of the 184 advisories thatpnpm auditreported, and it now reports none.docs/and both examples are now workspace members that use one root lockfile. Their separatepnpm-lock.yamlfiles are deleted.tsconfig.jsonis now typecheck-only and covers all ofsrc, including the six spec files thatpnpm typecheckpreviously skipped.- Removed: the unused
.npmignore,scripts/resolve-catalog.mjs,scripts/upgrade_deps.sh, and the TypeScript 6 SDK path.
👷 CI and Release Pipeline
- CI runs on Node 26, and every GitHub Action moves to its latest major version. A local composite action (
.github/actions/setup/action.yml) replaces the setup steps each job used to repeat. - The security job runs
pnpm auditacross all dependencies, dev included. Advisories of any severity fail CI. - A new Examples job builds both example apps against the freshly built package.
- Releases are packed with
pnpm pack, which resolvescatalog:andworkspace:natively, and published to npm with provenance. Packing runs before the version bump is committed or tagged, so a pack failure no longer leaves an orphaned tag. - The publish job no longer caches dependencies, following
setup-node's trusted publishing guidance. - Release notes are generated with git-iris.
✅ Testing
- The integration test no longer marks
next-dynenvas aserverExternalPackagesentry, so it now renders the library through Next's bundler. That is how most apps consume it and the path thatsideEffects: falseaffects. - The integration test now polls for server readiness instead of assuming a fixed startup time.
- New regression tests cover the
nextScriptPropsstrategy merge, the propagatedDynamic server usageerror, and the parser andmakeEnvPublicedge cases.
Behavior Changes to Review
No public APIs were removed, but some edge cases now behave differently:
envParsers.number:- An empty or whitespace-only variable now returns
defaultValueinstead of0. Infinityand-Infinitynow throw.
- An empty or whitespace-only variable now returns
envParsers.boolean: values with surrounding whitespace are trimmed before matching, so' true 'now returnstrue.makeEnvPublic: a variable set to''is now copied toNEXT_PUBLIC_*instead of being skipped with a warning.EnvScript/PublicEnvScriptwithnonce={{ headerKey }}:Dynamic server usageerrors are no longer swallowed during prerendering. Next.js now treats the route as dynamic instead of rendering it statically without a nonce.nextScriptProps: these props are now merged overstrategy: 'beforeInteractive'instead of replacing it. If you passed props without astrategy, the script now loadsbeforeInteractive.
Upgrade Notes
- Move
makeEnvPubliccalls fromnext.configintoregister()ininstrumentation.tsif you deploy withoutput: 'standalone'. - If you set
nextScriptProps.strategyto something other thanbeforeInteractive, remove it. Client components can readundefinedwith a later strategy. - If you use
disableNextScriptunder a CSP, passnonceexplicitly. - CommonJS consumers, including a compiled
next.config.ts, can nowrequire('next-dynenv')on Node 20.19+ or 22.12+. - The peer dependency range is unchanged:
next^15 || ^16,react^19, and Node>=20.9.0.