fix(nextjs): make built next.config load without @nx/next installed - #36655
Conversation
✅ Deploy Preview for nx-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for nx-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
View your CI Pipeline Execution ↗ for commit f7e5c60
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Nx Cloud has identified a flaky task in your failed CI:
🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.
🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.
🎓 Learn more about Self-Healing CI on nx.dev
AgentEnder
left a comment
There was a problem hiding this comment.
Approving. I verified the fix in a container with no @nx/* reachable: base crashes with Cannot find module './deprecation', HEAD starts clean. The warning still fires on nx build and stays silent on next start.
One thing worth changing, though it does not block.
.oxlintrc.json:99: adding compose-plugins.ts to the with-nx.ts override replaces the top-level no-restricted-imports for that file rather than merging with it. Same file, same import injected at line 1, real oxlint:
| import | base | HEAD |
|---|---|---|
'./deprecation' |
ok | error |
'@nx/devkit' |
ok | error |
'nx' |
error | ok |
'chalk' |
error | ok |
'fs-extra' |
error | ok |
The two you gain are the two that matter for #36511. But bare 'nx' is the same failure class: a top-level import { workspaceRoot } from 'nx' here emits require("nx") into the copied helper and dies in a pruned container exactly like ./deprecation did. It escapes because the override's group is nx/**/*, which does not match the bare specifier, and the new e2e check only validates relative requires. Nothing is broken today, there are no bare nx imports in packages/next. One line: make the group ["nx", "nx/**/*"], and re-add chalk and fs-extra to its paths.
Good catch on the dead assertions. They compared nextConfigPath, the path string rather than the contents, so they could never fail, and they sat in a test skipped since 2024.
Since 23.0.0, apps built with the @nx/next:build executor crash at next start in production containers where @nx/next is not installed: Error: Cannot find module './deprecation' Require stack: - <app>/.nx-helpers/compose-plugins.js - <app>/.nx-helpers/compiled.js - <app>/next.config.js The build executor copies the compiled utils/compose-plugins.js verbatim into .nx-helpers so the output loads without @nx/next, but the composePlugins deprecation warning added a top-level import of ./deprecation, which is not copied alongside the helper and itself requires @nx/devkit at load time. Drop the top-level import and lazily resolve the deprecation warning from the workspace inside the composed config function, guarded to the active Nx-task path, mirroring plugins/with-nx.ts. The copied helper now loads with no @nx/* packages present, and the warning still fires when composePlugins runs as part of an Nx task.
…d e2e checks
The guardrails for the .nx-helpers contract both had holes that let the
missing ./deprecation regression through. The no-restricted-imports rule
banning relative imports ("Relative files are not available in dist")
only covered plugins/with-nx.ts, not utils/compose-plugins.ts, which is
copied into the output the same way. The e2e assertion for bad imports
compared the next.config.js path string instead of its contents, so it
could never fail, and it lived in a test that has been skipped since
2024.
Extend the lint rule to utils/compose-plugins.ts, and replace the dead
assertion with a content-based check shared by the active legacy build
test: the rewritten next.config.js must not require dev-only packages,
and every relative require in the copied .nx-helpers files must resolve
to a file present in the build output.
The no-restricted-imports override guarding the files copied into
.nx-helpers replaces the package-level rule instead of merging with it,
so anything it does not restate goes unenforced there. Adding
src/utils/compose-plugins.ts to that override dropped the chalk,
fs-extra and bare nx restrictions the file used to inherit. A top-level
nx import emits require("nx") into the copied helper and fails on a
pruned install the same way ./deprecation did, because
update-package-json.ts never adds nx to the generated package.json.
The relative-import patterns had a separate gap. They matched ./**/* and
**/src/**/*, which cover plugins/with-nx.ts because it sits outside src,
but let parent-relative specifiers through from a file inside src.
Restate chalk and fs-extra, add the bare specifier to the nx group, and
match `.`, `..`, `./**` and `../**` so any relative import is caught from
either file. The glob forms need a path segment after the separator, so
the two exact entries are what cover a bare `.` or `..`.
Current Behavior
Since
@nx/next@23.0.0, an app built with the@nx/next:buildexecutor crashes atnext startin a production container where@nx/nextis not installed:The executor copies the compiled
utils/compose-plugins.jsverbatim into.nx-helpers, so the output loads without@nx/next. ThecomposePluginsdeprecation warning added a top-level import of./deprecation. That file is not copied alongside the helper, and it requires@nx/devkitat load time.Expected Behavior
The build output is self-contained again. The copied helper has no top-level imports, so
next startworks with only the app's production dependencies installed. The deprecation warning still fires whencomposePluginsruns as part of an Nx task. Nx resolves it lazily from the workspace, behind the same phase guardplugins/with-nx.tsuses, so the production server phase never loads an@nx/*package.Related Issue(s)
Fixes #36511
Implementation Notes
plugins/with-nx.ts, and missed parent-relative specifiers there. It now coversutils/compose-plugins.tstoo, and rejects any relative ornximport from either file.next.config.jspath string, not its contents, in a test skipped since 2024. The active legacy build test now reads the contents, and resolves every relative require in the copied.nx-helpersfiles.View Polygraph session ↗