Skip to content

fix(nextjs): make built next.config load without @nx/next installed - #36655

Merged
leosvelperez merged 3 commits into
masterfrom
gh-36511
Aug 31, 2026
Merged

fix(nextjs): make built next.config load without @nx/next installed#36655
leosvelperez merged 3 commits into
masterfrom
gh-36511

Conversation

@leosvelperez

@leosvelperez leosvelperez commented Aug 13, 2026

Copy link
Copy Markdown
Member

Current Behavior

Since @nx/next@23.0.0, an app built with the @nx/next:build executor crashes at next start in a production container 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 executor copies the compiled utils/compose-plugins.js verbatim into .nx-helpers, so the output loads without @nx/next. The composePlugins deprecation warning added a top-level import of ./deprecation. That file is not copied alongside the helper, and it requires @nx/devkit at load time.

Expected Behavior

The build output is self-contained again. The copied helper has no top-level imports, so next start works with only the app's production dependencies installed. The deprecation warning still fires when composePlugins runs as part of an Nx task. Nx resolves it lazily from the workspace, behind the same phase guard plugins/with-nx.ts uses, so the production server phase never loads an @nx/* package.

Related Issue(s)

Fixes #36511

Implementation Notes

  • The regression got past two guardrails, and both are now closed.
  • The lint rule banning relative imports in the copied helpers covered only plugins/with-nx.ts, and missed parent-relative specifiers there. It now covers utils/compose-plugins.ts too, and rejects any relative or nx import from either file.
  • The e2e bad-imports assertion compared the next.config.js path 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-helpers files.

View Polygraph session ↗

@netlify

netlify Bot commented Aug 13, 2026

Copy link
Copy Markdown

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit f7e5c60
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6a954757032ea30008e67672
😎 Deploy Preview https://deploy-preview-36655--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Aug 13, 2026

Copy link
Copy Markdown

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit f7e5c60
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6a9547574db65800083a0d5f
😎 Deploy Preview https://deploy-preview-36655--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@leosvelperez leosvelperez self-assigned this Aug 13, 2026
@nx-cloud

nx-cloud Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit f7e5c60

Command Status Duration Result
nx affected --targets=lint,oxlint,test,build,e2... ✅ Succeeded 52m 5s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 4s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 1m 2s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 19s View ↗
nx-cloud record -- nx format:check ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-31 10:17:14 UTC

@leosvelperez
leosvelperez marked this pull request as ready for review August 13, 2026 13:51
@leosvelperez
leosvelperez requested a review from a team as a code owner August 13, 2026 13:51
nx-cloud[bot]

This comment was marked as outdated.

@nx-cloud nx-cloud Bot left a comment

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.

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.

Nx Cloud View detailed reasoning in Nx Cloud ↗

🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.


🎓 Learn more about Self-Healing CI on nx.dev

@AgentEnder AgentEnder left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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 `..`.
@leosvelperez
leosvelperez enabled auto-merge (squash) August 31, 2026 10:01
@leosvelperez
leosvelperez merged commit 7baa099 into master Aug 31, 2026
26 checks passed
@leosvelperez
leosvelperez deleted the gh-36511 branch August 31, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@nx/next:build produces a broken standalone output: Cannot find module './deprecation' at runtime

2 participants