Skip to content

fix: ensure nextjs picks up the correct config file when called by nextjs_standalone_build()#2778

Merged
jbedard merged 5 commits intoaspect-build:mainfrom
MatrixFrog:tyler/next-fix
Mar 31, 2026
Merged

fix: ensure nextjs picks up the correct config file when called by nextjs_standalone_build()#2778
jbedard merged 5 commits intoaspect-build:mainfrom
MatrixFrog:tyler/next-fix

Conversation

@MatrixFrog
Copy link
Copy Markdown
Contributor

@MatrixFrog MatrixFrog commented Mar 19, 2026

Without this change, there are two next config files available to the next binary: the one the user passed in, and next.bazel.mjs, which has been renamed to next.config.mjs by copy_file(). We want it to use the next.bazel.mjs one as the config file, but if the one from the user is named next.config.js, then it gets picked up by the next CLI instead.

By renaming the user's config (prefixing it with __original), we ensure that next will pick up the one we want it to.

Fix #2789

Changes are visible to end-users: yes

  • Searched for relevant documentation and updated as needed: I don't think there is any docs update needed
  • Breaking change (forces users to change their own code or config): I suppose it could be, if someone was inadvertently depending on the current behavior, but it seems unlikely.
  • Suggested release notes appear below: no

Test plan

  • New test cases added
  • Manual testing

@aspect-workflows
Copy link
Copy Markdown

aspect-workflows Bot commented Mar 19, 2026

Bazel 7 (Test)

All tests were cache hits

319 tests (100.0%) were fully cached saving 42s.


Bazel 8 (Test)

All tests were cache hits

277 tests (100.0%) were fully cached saving 35s.


Bazel 9 (Test)

All tests were cache hits

277 tests (100.0%) were fully cached saving 39s.


Bazel 7 (Test)

e2e/bzlmod

All tests were cache hits

7 tests (100.0%) were fully cached saving 664ms.


Bazel 8 (Test)

e2e/bzlmod

All tests were cache hits

7 tests (100.0%) were fully cached saving 642ms.


Bazel 9 (Test)

e2e/bzlmod

All tests were cache hits

7 tests (100.0%) were fully cached saving 596ms.


Bazel 7 (Test)

e2e/git_dep_metadata

All tests were cache hits

1 test (100.0%) was fully cached saving 30ms.


Bazel 8 (Test)

e2e/git_dep_metadata

All tests were cache hits

1 test (100.0%) was fully cached saving 26ms.


Bazel 9 (Test)

e2e/git_dep_metadata

All tests were cache hits

1 test (100.0%) was fully cached saving 30ms.


Bazel 7 (Test)

e2e/gyp_no_install_script

All tests were cache hits

2 tests (100.0%) were fully cached saving 112ms.


Bazel 8 (Test)

e2e/gyp_no_install_script

All tests were cache hits

1 test (100.0%) was fully cached saving 50ms.


Bazel 9 (Test)

e2e/gyp_no_install_script

All tests were cache hits

1 test (100.0%) was fully cached saving 46ms.


Bazel 7 (Test)

e2e/js_binary_workspace

All tests were cache hits

1 test (100.0%) was fully cached saving 44ms.


Bazel 8 (Test)

e2e/js_binary_workspace

All tests were cache hits

1 test (100.0%) was fully cached saving 30ms.


Bazel 9 (Test)

e2e/js_binary_workspace

All tests were cache hits

1 test (100.0%) was fully cached saving 35ms.


Bazel 7 (Test)

e2e/js_image_oci

Buildkite build #12464 is running...


Bazel 7 (Test)

e2e/nextjs

All tests were cache hits

3 tests (100.0%) were fully cached saving 165ms.


Bazel 8 (Test)

e2e/nextjs

All tests were cache hits

3 tests (100.0%) were fully cached saving 94ms.


Bazel 9 (Test)

e2e/nextjs

All tests were cache hits

3 tests (100.0%) were fully cached saving 112ms.


Bazel 7 (Test)

e2e/npm_link_package

All tests were cache hits

2 tests (100.0%) were fully cached saving 172ms.


Bazel 8 (Test)

e2e/npm_link_package

All tests were cache hits

2 tests (100.0%) were fully cached saving 137ms.


Bazel 9 (Test)

e2e/npm_link_package

All tests were cache hits

2 tests (100.0%) were fully cached saving 170ms.


Bazel 7 (Test)

e2e/npm_link_package-esm

All tests were cache hits

2 tests (100.0%) were fully cached saving 189ms.


Bazel 8 (Test)

e2e/npm_link_package-esm

All tests were cache hits

2 tests (100.0%) were fully cached saving 138ms.


Bazel 9 (Test)

e2e/npm_link_package-esm

All tests were cache hits

2 tests (100.0%) were fully cached saving 173ms.


Bazel 7 (Test)

e2e/npm_link_package-rerooted

All tests were cache hits

2 tests (100.0%) were fully cached saving 207ms.


Bazel 8 (Test)

e2e/npm_link_package-rerooted

All tests were cache hits

2 tests (100.0%) were fully cached saving 139ms.


Bazel 9 (Test)

e2e/npm_link_package-rerooted

All tests were cache hits

2 tests (100.0%) were fully cached saving 186ms.


Bazel 7 (Test)

e2e/npm_translate_lock

All tests were cache hits

3 tests (100.0%) were fully cached saving 383ms.


Bazel 8 (Test)

e2e/npm_translate_lock

All tests were cache hits

3 tests (100.0%) were fully cached saving 287ms.


Bazel 9 (Test)

e2e/npm_translate_lock

All tests were cache hits

3 tests (100.0%) were fully cached saving 289ms.


Bazel 7 (Test)

e2e/npm_translate_lock_disable_hooks

All tests were cache hits

3 tests (100.0%) were fully cached saving 268ms.


Bazel 8 (Test)

e2e/npm_translate_lock_disable_hooks

All tests were cache hits

1 test (100.0%) was fully cached saving 62ms.


Bazel 9 (Test)

e2e/npm_translate_lock_disable_hooks

All tests were cache hits

1 test (100.0%) was fully cached saving 32ms.


Bazel 7 (Test)

e2e/npm_translate_lock_empty

All tests were cache hits

2 tests (100.0%) were fully cached saving 132ms.


Bazel 8 (Test)

e2e/npm_translate_lock_empty

All tests were cache hits

2 tests (100.0%) were fully cached saving 114ms.


Bazel 9 (Test)

e2e/npm_translate_lock_empty

All tests were cache hits

2 tests (100.0%) were fully cached saving 105ms.


Bazel 7 (Test)

e2e/npm_translate_lock_exclude_package_contents

All tests were cache hits

1 test (100.0%) was fully cached saving 33ms.


Bazel 8 (Test)

e2e/npm_translate_lock_exclude_package_contents

All tests were cache hits

1 test (100.0%) was fully cached saving 21ms.


Bazel 9 (Test)

e2e/npm_translate_lock_exclude_package_contents

All tests were cache hits

1 test (100.0%) was fully cached saving 86ms.


Bazel 7 (Test)

e2e/npm_translate_lock_multi

All tests were cache hits

2 tests (100.0%) were fully cached saving 106ms.


Bazel 8 (Test)

e2e/npm_translate_lock_multi

All tests were cache hits

2 tests (100.0%) were fully cached saving 54ms.


Bazel 9 (Test)

e2e/npm_translate_lock_multi

All tests were cache hits

2 tests (100.0%) were fully cached saving 113ms.


Bazel 7 (Test)

e2e/npm_translate_lock_partial_clone

All tests were cache hits

1 test (100.0%) was fully cached saving 26ms.


Bazel 8 (Test)

e2e/npm_translate_lock_partial_clone

All tests were cache hits

1 test (100.0%) was fully cached saving 30ms.


Bazel 9 (Test)

e2e/npm_translate_lock_partial_clone

All tests were cache hits

1 test (100.0%) was fully cached saving 38ms.


Bazel 7 (Test)

e2e/npm_translate_lock_replace_packages

All tests were cache hits

4 tests (100.0%) were fully cached saving 321ms.


Bazel 8 (Test)

e2e/npm_translate_lock_replace_packages

All tests were cache hits

4 tests (100.0%) were fully cached saving 249ms.


Bazel 9 (Test)

e2e/npm_translate_lock_replace_packages

All tests were cache hits

4 tests (100.0%) were fully cached saving 320ms.


Bazel 7 (Test)

e2e/npm_translate_lock_subdir_patch

All tests were cache hits

1 test (100.0%) was fully cached saving 50ms.


Bazel 8 (Test)

e2e/npm_translate_lock_subdir_patch

All tests were cache hits

1 test (100.0%) was fully cached saving 67ms.


Bazel 9 (Test)

e2e/npm_translate_lock_subdir_patch

All tests were cache hits

1 test (100.0%) was fully cached saving 50ms.


Bazel 7 (Test)

e2e/npm_translate_package_lock

All tests were cache hits

1 test (100.0%) was fully cached saving 25ms.


Bazel 8 (Test)

e2e/npm_translate_package_lock

All tests were cache hits

1 test (100.0%) was fully cached saving 62ms.


Bazel 9 (Test)

e2e/npm_translate_package_lock

All tests were cache hits

1 test (100.0%) was fully cached saving 32ms.


Bazel 7 (Test)

e2e/npm_translate_yarn_lock

All tests were cache hits

1 test (100.0%) was fully cached saving 25ms.


Bazel 8 (Test)

e2e/npm_translate_yarn_lock

All tests were cache hits

1 test (100.0%) was fully cached saving 62ms.


Bazel 9 (Test)

e2e/npm_translate_yarn_lock

All tests were cache hits

1 test (100.0%) was fully cached saving 32ms.


Bazel 7 (Test)

e2e/output_paths

All tests were cache hits

2 tests (100.0%) were fully cached saving 213ms.


Bazel 8 (Test)

e2e/output_paths

All tests were cache hits

2 tests (100.0%) were fully cached saving 252ms.


Bazel 9 (Test)

e2e/output_paths

All tests were cache hits

2 tests (100.0%) were fully cached saving 171ms.


Bazel 7 (Test)

e2e/package_json_module

All tests were cache hits

1 test (100.0%) was fully cached saving 195ms.


Bazel 8 (Test)

e2e/package_json_module

All tests were cache hits

1 test (100.0%) was fully cached saving 192ms.


Bazel 9 (Test)

e2e/package_json_module

All tests were cache hits

1 test (100.0%) was fully cached saving 190ms.


Bazel 7 (Test)

e2e/patch_from_repo

All tests were cache hits

1 test (100.0%) was fully cached saving 25ms.


Bazel 7 (Test)

e2e/pnpm_lockfiles

All tests were cache hits

41 tests (100.0%) were fully cached saving 4s.


Bazel 8 (Test)

e2e/pnpm_lockfiles

All tests were cache hits

13 tests (100.0%) were fully cached saving 1s.


Bazel 9 (Test)

e2e/pnpm_lockfiles

All tests were cache hits

13 tests (100.0%) were fully cached saving 955ms.


Bazel 7 (Test)

e2e/pnpm_repo_install

All tests were cache hits

1 test (100.0%) was fully cached saving 746ms.


Bazel 8 (Test)

e2e/pnpm_repo_install

All tests were cache hits

1 test (100.0%) was fully cached saving 772ms.


Bazel 9 (Test)

e2e/pnpm_repo_install

All tests were cache hits

1 test (100.0%) was fully cached saving 738ms.


Bazel 7 (Test)

e2e/pnpm_version

All tests were cache hits

1 test (100.0%) was fully cached saving 44ms.


Bazel 8 (Test)

e2e/pnpm_version

All tests were cache hits

1 test (100.0%) was fully cached saving 44ms.


Bazel 9 (Test)

e2e/pnpm_version

All tests were cache hits

1 test (100.0%) was fully cached saving 42ms.


Bazel 7 (Test)

e2e/pnpm_workspace

All tests were cache hits

15 tests (100.0%) were fully cached saving 2s.


Bazel 8 (Test)

e2e/pnpm_workspace

All tests were cache hits

14 tests (100.0%) were fully cached saving 2s.


Bazel 9 (Test)

e2e/pnpm_workspace

All tests were cache hits

14 tests (100.0%) were fully cached saving 2s.


Bazel 7 (Test)

e2e/pnpm_workspace_deps

All tests were cache hits

3 tests (100.0%) were fully cached saving 212ms.


Bazel 8 (Test)

e2e/pnpm_workspace_deps

All tests were cache hits

3 tests (100.0%) were fully cached saving 278ms.


Bazel 9 (Test)

e2e/pnpm_workspace_deps

All tests were cache hits

3 tests (100.0%) were fully cached saving 237ms.


Bazel 7 (Test)

e2e/pnpm_workspace_rerooted

Buildkite build #12464 is running...


Bazel 8 (Test)

e2e/pnpm_workspace_rerooted

Buildkite build #12464 is running...


Bazel 9 (Test)

e2e/pnpm_workspace_rerooted

All tests were cache hits

14 tests (100.0%) were fully cached saving 2s.


Bazel 7 (Test)

e2e/repo_mapping

All tests were cache hits

3 tests (100.0%) were fully cached saving 222ms.


Bazel 8 (Test)

e2e/repo_mapping

All tests were cache hits

3 tests (100.0%) were fully cached saving 154ms.


Bazel 9 (Test)

e2e/repo_mapping

All tests were cache hits

3 tests (100.0%) were fully cached saving 308ms.


Bazel 7 (Test)

e2e/runfiles

All tests were cache hits

6 tests (100.0%) were fully cached saving 660ms.


Bazel 8 (Test)

e2e/runfiles

All tests were cache hits

6 tests (100.0%) were fully cached saving 693ms.


Bazel 9 (Test)

e2e/runfiles

All tests were cache hits

6 tests (100.0%) were fully cached saving 921ms.


Bazel 7 (Test)

e2e/stamped_package_json

All tests were cache hits

1 test (100.0%) was fully cached saving 48ms.


Bazel 8 (Test)

e2e/stamped_package_json

All tests were cache hits

1 test (100.0%) was fully cached saving 24ms.


Bazel 9 (Test)

e2e/stamped_package_json

Buildkite build #12464 is running...


Bazel 7 (Test)

e2e/vendored_node

All tests were cache hits

1 test (100.0%) was fully cached saving 61ms.


Bazel 8 (Test)

e2e/vendored_node

All tests were cache hits

1 test (100.0%) was fully cached saving 57ms.


Bazel 9 (Test)

e2e/vendored_node

Buildkite build #12464 is running...


Bazel 7 (Test)

e2e/vendored_tarfile

Waiting for runner...


Bazel 8 (Test)

e2e/vendored_tarfile

All tests were cache hits

1 test (100.0%) was fully cached saving 62ms.


Bazel 9 (Test)

e2e/vendored_tarfile

Buildkite build #12464 is running...


Bazel 7 (Test)

e2e/verify_patches

All tests were cache hits

2 tests (100.0%) were fully cached saving 92ms.


Bazel 8 (Test)

e2e/verify_patches

Buildkite build #12464 is running...


Bazel 9 (Test)

e2e/verify_patches

Buildkite build #12464 is running...


Bazel 7 (Test)

e2e/worker

Buildkite build #12464 is running...


Bazel 8 (Test)

e2e/worker

All tests were cache hits

1 test (100.0%) was fully cached saving 50ms.


Bazel 9 (Test)

e2e/worker

Buildkite build #12464 is running...


Bazel 7 (Test)

examples/protobuf/bufbuild-es

All tests were cache hits

1 test (100.0%) was fully cached saving 120ms.


Bazel 8 (Test)

examples/protobuf/bufbuild-es

All tests were cache hits

1 test (100.0%) was fully cached saving 132ms.


Bazel 9 (Test)

examples/protobuf/bufbuild-es

All tests were cache hits

1 test (100.0%) was fully cached saving 116ms.


Bazel 7 (Test)

examples/protobuf/google-protobuf

All tests were cache hits

1 test (100.0%) was fully cached saving 97ms.


Bazel 8 (Test)

examples/protobuf/google-protobuf

All tests were cache hits

1 test (100.0%) was fully cached saving 64ms.


Bazel 9 (Test)

examples/protobuf/google-protobuf

All tests were cache hits

1 test (100.0%) was fully cached saving 101ms.


Buildifier      Format

@MatrixFrog MatrixFrog changed the title Fix for Zipline issue Fix for nextjs config file issue Mar 19, 2026
@MatrixFrog MatrixFrog changed the title Fix for nextjs config file issue Make sure next picks up the correct config file when called by nextjs_standalone_build() Mar 20, 2026
Comment thread contrib/nextjs/defs.bzl
@jbedard
Copy link
Copy Markdown
Member

jbedard commented Mar 30, 2026

@MatrixFrog after testing this more and asking robots to write tests or better solutions I think what you have here might be as good as we'll get, at least without a lot of extra complexity.

Was there anything else you wanted to do here before merging?

@MatrixFrog
Copy link
Copy Markdown
Contributor Author

Was there anything else you wanted to do here before merging?

I mean, probably, since I left it in draft state, but I don't remember what 🙃 so I guess go ahead and merge this if you're happy with it.

@MatrixFrog MatrixFrog marked this pull request as ready for review March 30, 2026 23:48
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4cf30d169

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread contrib/nextjs/defs.bzl Outdated
@jbedard
Copy link
Copy Markdown
Member

jbedard commented Mar 30, 2026

Well my remaining concerns:

  • next.config.js still takes priority if it somehow ends up in bazel-bin
  • copying files just for the purpose of renaming to workaround a bug is lame

But I can't think of any (simple) way around this. We could have a simple check for files in srcs but that won't work for targets in srcs, I can't think of any simple way to have a runtime check. We could throw an error if config == "next.config.js" but that won't work for config == ":next_config" etc.

So I think your change is a dx improvement even if not perfect and even without automated tests 🤷

@MatrixFrog
Copy link
Copy Markdown
Contributor Author

Yeah it's definitely a bit weird. If I were the king of all frontend-dom I would make it so next takes an optional --config=filename flag so that you can just tell it explicitly which file to use. I'm happy to go in and try writing a few tests though it might not be this week

jbedard added a commit to jbedard/rules_js that referenced this pull request Mar 31, 2026
Next.js discovers config files in priority order: next.config.js >
next.config.mjs > next.config.ts. When the user's config is named
next.config.js, it was loaded directly by the CLI, bypassing the
next.bazel.mjs wrapper and leaving .aspect_rules_js symlinks in the
standalone output.

Fix (from aspect-build#2778): rename the user's config to __original.<basename> so
Next.js cannot discover it. Use config_basename extracted from the label
to handle label forms like :next.config.js or //pkg:next.config.js.

Add a fail() at analysis time if the config file also appears directly
in srcs or data, which would reintroduce the bypass even with the rename.

Add e2e tests covering four config variants (next.config.js CJS,
next.config.js ESM via "type":"module", next.config.mjs, next.config.cjs)
with sh_test regression tests that verify standalone/node_modules contains
no .aspect_rules_js symlinks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jbedard added a commit to jbedard/rules_js that referenced this pull request Mar 31, 2026
Next.js discovers config files in priority order: next.config.js >
next.config.mjs > next.config.ts. When the user's config is named
next.config.js, it was loaded directly by the CLI, bypassing the
next.bazel.mjs wrapper and leaving .aspect_rules_js symlinks in the
standalone output.

Fix (from aspect-build#2778): rename the user's config to __original.<basename> so
Next.js cannot discover it. Use config_basename extracted from the label
to handle label forms like :next.config.js or //pkg:next.config.js.

Add a fail() at analysis time if the config file also appears directly
in srcs or data, which would reintroduce the bypass even with the rename.

Add e2e tests covering four config variants (next.config.js CJS,
next.config.js ESM via "type":"module", next.config.mjs, next.config.cjs)
with sh_test regression tests that verify standalone/node_modules contains
no .aspect_rules_js symlinks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jbedard added a commit to jbedard/rules_js that referenced this pull request Mar 31, 2026
Next.js discovers config files in priority order: next.config.js >
next.config.mjs > next.config.ts. When the user's config is named
next.config.js, it was loaded directly by the CLI, bypassing the
next.bazel.mjs wrapper and leaving .aspect_rules_js symlinks in the
standalone output.

Fix (from aspect-build#2778): rename the user's config to __original.<basename> so
Next.js cannot discover it. Use config_basename extracted from the label
to handle label forms like :next.config.js or //pkg:next.config.js.

Add a fail() at analysis time if the config file also appears directly
in srcs or data, which would reintroduce the bypass even with the rename.

Add e2e tests covering four config variants (next.config.js CJS,
next.config.js ESM via "type":"module", next.config.mjs, next.config.cjs)
with sh_test regression tests that verify standalone/node_modules contains
no .aspect_rules_js symlinks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jbedard added a commit to jbedard/rules_js that referenced this pull request Mar 31, 2026
Next.js discovers config files in priority order: next.config.js >
next.config.mjs > next.config.ts. When the user's config is named
next.config.js, it was loaded directly by the CLI, bypassing the
next.bazel.mjs wrapper and leaving .aspect_rules_js symlinks in the
standalone output.

Fix (from aspect-build#2778): rename the user's config to __original.<basename> so
Next.js cannot discover it. Use config_basename extracted from the label
to handle label forms like :next.config.js or //pkg:next.config.js.

Add a fail() at analysis time if the config file also appears directly
in srcs or data, which would reintroduce the bypass even with the rename.

Add e2e tests covering four config variants (next.config.js CJS,
next.config.js ESM via "type":"module", next.config.mjs, next.config.cjs)
with sh_test regression tests that verify standalone/node_modules contains
no .aspect_rules_js symlinks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jbedard jbedard force-pushed the tyler/next-fix branch 3 times, most recently from d488e49 to 6436a7c Compare March 31, 2026 00:46
@jbedard
Copy link
Copy Markdown
Member

jbedard commented Mar 31, 2026

@MatrixFrog does the additional test and minor change I pushed look ok? It will be squashed with your commit when this merges...

@MatrixFrog
Copy link
Copy Markdown
Contributor Author

cc @tyler-breisacher-zipline so that I'll get an email about this on my other account

@jbedard jbedard changed the title Make sure next picks up the correct config file when called by nextjs_standalone_build() fix: ensure nextjs picks up the correct config file when called by nextjs_standalone_build() Mar 31, 2026
@jbedard jbedard enabled auto-merge (squash) March 31, 2026 21:08
@jbedard jbedard merged commit 44a9f19 into aspect-build:main Mar 31, 2026
16 checks passed
@MatrixFrog MatrixFrog deleted the tyler/next-fix branch April 8, 2026 02:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: next.config.js may take precedence over generated next.config.mjs

3 participants