fix: Keep the rewrites when next.config.js is an ES module - #81
Merged
Conversation
copyNextjsConfig loads next.config.js with a require and only falls back to import when that throws. The require returns a module namespace rather than the config for an ES module in two cases: since Node.js 22.12 require of an ES module succeeds and returns a namespace, and the nsm bin registers esbuild-runner, which transpiles an ES module to CommonJS and marks the result with __esModule. Reading the config off the namespace left rewrites undefined, so it was dropped from the generated .nsm/next.config.ts. A project that relies on a rewrite to reach its routes then 404s on every request, with no error to explain it. Unwrap the default export whenever a namespace is loaded, whichever way it was loaded. Resolve rewrites into a copy of the config, since a module namespace is frozen, and pass a file URL to the import fallback so it also works on Windows. Extract the loading into loadNextjsConfig and cover the CommonJS object, CommonJS function and ES module configs with tests. The sample project used by the existing tests has a CommonJS config, so the ES module path was untested. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6bQ6zQqwT1vq9NkrG1UTm
razor-x
pushed a commit
to seamapi/fake-seam-connect
that referenced
this pull request
Aug 12, 2026
The workaround regenerated .nsm/next.config.ts after nsm build to restore the rewrites nsm drops. Apply the upstream fix with patch-package instead, so nsm writes the config correctly in the first place. Run patch-package from prebuild:nsm rather than a lifecycle script. postinstall runs when the published package is installed as a dependency, where patch-package is absent, and prepare runs in the production install stage of the Dockerfile, which omits devDependencies. Only nsm build reads the patched file, so applying it there covers every caller. Remove the patch once a nextjs-server-modules release includes the fix. seamapi/nextjs-server-modules#81 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V6bQ6zQqwT1vq9NkrG1UTm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
copyNextjsConfigloadsnext.config.jswith arequireand only falls back toimportwhen that throws. For an ES module config therequireno longer throws, and returns a module namespace rather than the config, in two cases:requireof an ES module succeeds and returns a namespace.nsmbin registersesbuild-runner, which hooks.jsoutsidenode_modulesand transpiles an ES module to CommonJS, marking the result with__esModule.typeof nextConfig.rewrites === "function"is then false on the namespace, sorewritesis dropped from the generated.nsm/next.config.ts. There is no error — the build succeeds and the config comes out as:A project that relies on a rewrite to reach its routes then 404s on every request with nothing to explain it. This is what
seamapi/fake-seam-connecthit when moving to Node.js 22.The fix
Unwrap the default export whenever a namespace is loaded, whichever way it was loaded —
Symbol.toStringTag === "Module"covers Node's namespace,__esModulecovers esbuild's CommonJS output, and theimportfallback goes through the same unwrap.Two smaller correctness fixes in the same function:
rewritesinto a copy of the config. A module namespace is frozen, so assigning onto it fails.importfallback, so it also resolves on Windows.Tests
The loading is extracted into an exported
loadNextjsConfigso it can be tested directly, with fixtures for a CommonJS object config, a CommonJS function config and an ES module config. The sample project used by the existing tests has a CommonJS config, which is why this path was never covered.The new ES module test fails on
mainand passes with this change. All 12 tests pass, andprettier --checkis clean.Verified end to end against
seamapi/fake-seam-connecton Node.js 22.22: with this fix and no workaround in the consumer,nsm buildemits the rewrites correctly and the routes resolve.🤖 Generated with Claude Code
https://claude.ai/code/session_01V6bQ6zQqwT1vq9NkrG1UTm
Generated by Claude Code