Skip to content

Conversation

@Flo0806
Copy link
Member

@Flo0806 Flo0806 commented Nov 4, 2025

🔗 Linked issue

Fixes: #33587

Bug Description

Component priority doesn't respect layer order. All non-root layers receive the same priority value (0), which means when multiple layers define the same component, the resolution becomes unpredictable instead of following the documented layer priority order.


🔍 Root Cause

In packages/nuxt/src/components/module.ts:75:

priority: layer.config.srcDir === nuxt.options.srcDir ? 1 : 0

This assigns:

  • Root project: priority = 1
  • All other layers: priority = 0 ❌

When two layers have the same priority, component resolution doesn't follow the layer order - it becomes dependent on processing order, which is unpredictable.


✅ Solution

Assign priority based on layer index position:

const layerCount = nuxt.options._layers.length
for (let i = 0; i < layerCount; i++) {
const layer = nuxt.options._layers[i]
const priority = layerCount - i // Lower index = higher priority
// ...
}

This ensures the documented priority order is respected:

  1. Root project (index 0 → highest priority)
  2. Auto-scanned layers from ~/layers - alphabetically Z > A
  3. Layers in extends config - first entry > second entry

📚 Documentation Improvements

The layer priority documentation was confusing. The numbered list (1, 2, 3) read like a priority ranking, but it was actually describing the processing
order (where later entries can override earlier ones).

Changes:

  • Restructured to clearly show priority from highest to lowest
  • Added "When to Use Each" section explaining extends vs ~/layers
  • Added tip for controlling auto-scanned layer order with number prefixes (~/layers/1-base, ~/layers/2-theme)
  • Improved examples with explicit priority lists

📌 About the Linked Issue

The https://stackblitz.com/edit/github-xah69ved describes layers in ~/layers directory that are also listed in extends:

extends: ['./layers/pecav', './layers/webmail']

This is not a bug - it's the documented behavior:

  • Layers in ~/layers are auto-scanned with higher priority than extends
  • Auto-scanned layers are sorted alphabetically (Z > A)
  • Result: webmail (alphabetically later) overrides pecav

The actual bug was that component priority didn't respect this layer order at all - all layers had equal priority. This PR fixes that.

Solutions for the user:

  • Move layers outside ~/layers to control order via extends only
  • OR use number prefixes: ~/layers/1-pecav, ~/layers/2-webmail (2 has higher priority than 1)

🧪 Testing

All existing unit tests pass without modification. The layer loading order was already correct - only the component priority calculation was broken.

Manual testing confirms:

  • Root project components override all layers ✅
  • Auto-scanned layers override extends layers ✅
  • Layer order within each group is respected ✅

📝 Notes

  • Minimal change with no performance impact
  • Only affects component resolution when multiple layers define the same component
  • Aligns implementation with documented layer priority behavior

@Flo0806 Flo0806 requested a review from danielroe as a code owner November 4, 2025 10:10
@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 4, 2025

Open in StackBlitz

@nuxt/kit

npm i https://pkg.pr.new/@nuxt/kit@33654

nuxt

npm i https://pkg.pr.new/nuxt@33654

@nuxt/rspack-builder

npm i https://pkg.pr.new/@nuxt/rspack-builder@33654

@nuxt/schema

npm i https://pkg.pr.new/@nuxt/schema@33654

@nuxt/vite-builder

npm i https://pkg.pr.new/@nuxt/vite-builder@33654

@nuxt/webpack-builder

npm i https://pkg.pr.new/@nuxt/webpack-builder@33654

commit: ecf08de

@coderabbitai
Copy link

coderabbitai bot commented Nov 4, 2025

Walkthrough

This pull request changes component layer prioritisation in Nuxt: the module now iterates nuxt.options._layers with an index-aware loop and computes a per-layer priority as priority = layerCount - i (higher priority for earlier layers). That priority is passed into normalizeDirs when building layerDirs, altering how component directories are merged and overridden across layers. The change is implemented in packages/nuxt/src/components/module.ts. Two documentation pages (docs/1.getting-started/14.layers.md and docs/2.guide/3.going-further/7.layers.md) were rewritten to describe an explicit highest-to-lowest override order, guidance, tips and examples reflecting the new semantics. A new test packages/nuxt/test/component-layer-priority.test.ts was added to validate directory priority resolution. No public API signatures were changed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review packages/nuxt/src/components/module.ts for correct index-based iteration, correct priority = layerCount - i calculation and that priority is passed and respected by normalizeDirs.
  • Check normalizeDirs call sites and merging logic to ensure override direction matches the documented highest-to-lowest order.
  • Run and inspect the new test packages/nuxt/test/component-layer-priority.test.ts for correctness and test isolation (mocking of build/bundle steps).
  • Quick review of both updated docs for consistency with implemented behaviour and examples.

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the main change: fixing layer priority order for extends and auto-scanned layers, which aligns with the bug fix described in the changeset.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the bug, root cause, solution, documentation improvements, and testing.
Linked Issues check ✅ Passed The PR addresses all coding requirements from issue #33587: fixes priority calculation to respect layer order, ensures root project has highest priority, auto-scanned layers override extends, and includes a test validating the corrected priority resolution.
Out of Scope Changes check ✅ Passed All changes are within scope: component priority logic fix in module.ts, documentation clarification in layers.md files, and a new test file validating the priority behaviour. No unrelated changes detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 4, 2025

CodSpeed Performance Report

Merging #33654 will degrade performances by 11.24%

Comparing Flo0806:fix/layer-priority-order (ecf08de) with main (038f9d9)

Summary

❌ 1 regression
✅ 9 untouched

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark BASE HEAD Change
writeTypes in the basic-types fixture 80.1 ms 90.3 ms -11.24%

@danielroe
Copy link
Member

danielroe commented Nov 4, 2025

autoscanned layers should have higher priority than layers specified in extends in the nuxt config file, in the same way that the local project overrides any layers.

I'm pretty sure the docs are correct but if they are confusing we can update.

  1. Layers in extends - earlier entries have higher priority (first overrides second)
  2. Auto-scanned local layers from ~~/layers directory in alphabetical order (Z overrides A)
  3. Your project has the highest priority in the stack - it will always override other layers

for this example, 2 overrides 1, and 3 overrides 2.

@Flo0806
Copy link
Member Author

Flo0806 commented Nov 4, 2025

Okay, I think I understand now... But one question: What's the point of using extends in the config if the autoscanned layers still take precedence?

For example, if I have:

  • layers/pecav/ (autoscanned)
  • layers/webmail/ (autoscanned)
  • extends: ['./layers/pecav', './layers/webmail'] in config

Does the extends order matter at all, or will it always be alphabetical (webmail > pecav) because they're autoscanned?

I don't want to force a change – especially since that would be a break from the original behavior – it just seems a bit contradictory to me. When would you use extends vs. relying on autoscanning?

@danielroe
Copy link
Member

danielroe commented Nov 4, 2025

the point of extends is to specify non-local layers, e.g. an npm package.

if you need order in your local layers, do ~/layers/1.webmail, ~/layers/2.pecav

@Flo0806
Copy link
Member Author

Flo0806 commented Nov 4, 2025

Okay, I'll take another look at it. There's a minor bug in module.ts where all non-root layers get the same priority (0), which means the layer order isn't respected for component resolution.

The bigger issue, in my opinion, is the misleading documentation. The numbered list (1, 2, 3) reads like a priority order (highest to lowest), but it's actually the processing order where later entries override earlier ones. I'd like to improve the wording to make this clearer.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
docs/1.getting-started/14.layers.md (1)

91-95: Optional: Reduce word repetition in "When to Use Each" bullets.

The word "directory" appears at the end of the first bullet and start of the second, which can be smoothed by rewording one instance. For example, change the second bullet to:

- **`~~/layers` folder** - Use for local layers that are part of your project

Alternatively, consider:

- **Auto-scanned `~~/layers`** - Use for local layers that are part of your project

Both approaches preserve meaning while avoiding the repetition.

docs/2.guide/3.going-further/7.layers.md (1)

71-110: Documentation is consistent with getting‑started guide; same minor word repetition opportunity exists.

The Layer Priority section, When to Use Each guidance, and example scenario mirror the content in docs/1.getting-started/14.layers.md. The same word repetition of "directory" appears in the bullet points (lines ~81–82) and could be improved using the refactor suggested for file 1. Consider applying the refactor to both files simultaneously for consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 101e183 and b546242.

📒 Files selected for processing (3)
  • docs/1.getting-started/14.layers.md (1 hunks)
  • docs/2.guide/3.going-further/7.layers.md (1 hunks)
  • packages/nuxt/src/components/module.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/nuxt/src/components/module.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-11T12:34:22.648Z
Learnt from: Tofandel
Repo: nuxt/nuxt PR: 0
File: :0-0
Timestamp: 2024-11-11T12:34:22.648Z
Learning: Ensure that AI-generated summaries accurately reflect the key changes in the PR, focusing on notable changes such as the removal of unused imports and variables starting with underscores.

Applied to files:

  • docs/1.getting-started/14.layers.md
  • docs/2.guide/3.going-further/7.layers.md
🪛 LanguageTool
docs/1.getting-started/14.layers.md

[duplication] ~93-~93: Possible typo: you repeated a word.
Context: ...itories) or layers outside your project directory - ~~/layers directory - Use for local layers that are part ...

(ENGLISH_WORD_REPEAT_RULE)


[uncategorized] ~95-~95: Loose punctuation mark.
Context: ...l layers that are part of your project ::tip If you need to control the order of...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~97-97: Possible missing comma found.
Context: .../1.z-layer,
/layers/2.a-layer. This way 2.a-layer` will have higher priority t...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~97-~97: Loose punctuation mark.
Context: ... have higher priority than 1.z-layer. :: ### Example ```ts [nuxt.config.ts] e...

(UNLIKELY_OPENING_PUNCTUATION)

docs/2.guide/3.going-further/7.layers.md

[duplication] ~81-~81: Possible typo: you repeated a word.
Context: ...itories) or layers outside your project directory - ~~/layers directory - Use for local layers that are part ...

(ENGLISH_WORD_REPEAT_RULE)


[uncategorized] ~83-~83: Loose punctuation mark.
Context: ...l layers that are part of your project ::tip If you need to control the order of...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~85-85: Possible missing comma found.
Context: .../1.z-layer,
/layers/2.a-layer. This way 2.a-layer` will have higher priority t...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~85-~85: Loose punctuation mark.
Context: ... have higher priority than 1.z-layer. :: ### Example ```ts [nuxt.config.ts] e...

(UNLIKELY_OPENING_PUNCTUATION)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: test-fixtures (windows-latest, built, rspack, default, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (windows-latest, dev, vite, default, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (windows-latest, built, vite-env-api, default, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (windows-latest, built, vite-env-api, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (windows-latest, dev, vite, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (windows-latest, dev, vite, default, manifest-off, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-off, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, default, manifest-on, json, lts/-1)
  • GitHub Check: typecheck (ubuntu-latest, bundler)
  • GitHub Check: release-pkg-pr-new
  • GitHub Check: test-benchmark
  • GitHub Check: typecheck (windows-latest, bundler)
  • GitHub Check: test-size
  • GitHub Check: code
🔇 Additional comments (3)
docs/1.getting-started/14.layers.md (2)

83-90: Priority order documentation is accurate and well‑structured.

The numbered list clearly conveys the override hierarchy and provides specificity on auto-scanned layer alphabetical sorting and extends entry ordering. This aligns with the fix implemented in the PR.


100-122: Example clearly demonstrates layer priority with concrete scenario.

The introduction of the ~/layers/custom scenario and explicit priority stack provides learners with a concrete reference. The concluding explanation is clear and reinforces the documented behaviour.

docs/2.guide/3.going-further/7.layers.md (1)

275-277: Notes section reinforces layer priority model correctly.

The notes accurately state that earlier array items have higher priority and that the user's project is first. This aligns with and reinforces the documented priority order in the Layer Priority section.

danielroe and others added 3 commits November 5, 2025 10:53
Co-authored-by: Horu <73709188+HigherOrderLogic@users.noreply.github.com>
Copy link
Member

@danielroe danielroe left a comment

Choose a reason for hiding this comment

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

thank you ❤️

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/nuxt/test/component-layer-priority.test.ts (3)

28-30: Replace as any[] with proper typing.

The as any[] type assertion bypasses TypeScript's type checking, which could hide type mismatches or future API changes.

Apply this diff to use proper typing:

     nuxt.hook('components:dirs', (dirs) => {
-      captured.push(...dirs as any[])
+      captured.push(...dirs as ComponentsDir[])
     })

12-55: Consider adding try/finally for guaranteed cleanup.

If the test fails (e.g., assertion error at line 39), the nuxt.close() call at line 54 won't execute, potentially leaving the Nuxt instance open and causing resource leaks in the test environment.

Apply this diff to guarantee cleanup:

   it('assigns priorities to component dirs based on layer order', async () => {
     const captured: ComponentsDir[] = []
 
     const nuxt = await loadNuxt({
       cwd: layerFixtureDir,
       ready: true,
       overrides: {
         builder: {
           bundle: () => {
             nuxt.hooks.removeAllHooks()
             return Promise.resolve()
           },
         },
       },
     })
 
-    nuxt.hook('components:dirs', (dirs) => {
-      captured.push(...dirs as any[])
-    })
-
-    await buildNuxt(nuxt)
-
-    const dirs = captured.map(dir => [
-      dir.path.replace(layerFixtureDir, '<root>'),
-      dir.priority,
-    ])
-
-    expect(Object.fromEntries(dirs)).toStrictEqual({
-      // user project: highest priority
-      '<root>/components': 3,
-      '<root>/components/global': 3,
-      '<root>/components/islands': 3,
-      // local layer
-      '<root>/layers/auto/components': 2,
-      '<root>/layers/auto/components/global': 2,
-      '<root>/layers/auto/components/islands': 2,
-      // explicitly extended layer
-      '<root>/custom/components': 1,
-      '<root>/custom/components/global': 1,
-      '<root>/custom/components/islands': 1,
-    })
-
-    await nuxt.close()
+    try {
+      nuxt.hook('components:dirs', (dirs) => {
+        captured.push(...dirs as any[])
+      })
+
+      await buildNuxt(nuxt)
+
+      const dirs = captured.map(dir => [
+        dir.path.replace(layerFixtureDir, '<root>'),
+        dir.priority,
+      ])
+
+      expect(Object.fromEntries(dirs)).toStrictEqual({
+        // user project: highest priority
+        '<root>/components': 3,
+        '<root>/components/global': 3,
+        '<root>/components/islands': 3,
+        // local layer
+        '<root>/layers/auto/components': 2,
+        '<root>/layers/auto/components/global': 2,
+        '<root>/layers/auto/components/islands': 2,
+        // explicitly extended layer
+        '<root>/custom/components': 1,
+        '<root>/custom/components/global': 1,
+        '<root>/custom/components/islands': 1,
+      })
+    } finally {
+      await nuxt.close()
+    }
   })

39-52: Test coverage validates the core fix.

The assertions correctly validate the priority ordering described in the PR: root project (3) > auto-scanned layers (2) > extended layers (1).

For more comprehensive coverage, consider adding tests for edge cases in follow-up work:

  • No layers (only root project)
  • Multiple auto-scanned layers (verifying alphabetical sort)
  • Multiple extended layers (verifying order)
  • Only extended layers (no auto-scanned)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a35e25 and ecf08de.

📒 Files selected for processing (1)
  • packages/nuxt/test/component-layer-priority.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow standard TypeScript conventions and best practices

Files:

  • packages/nuxt/test/component-layer-priority.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write unit tests for core functionality using vitest

Files:

  • packages/nuxt/test/component-layer-priority.test.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: huang-julien
Repo: nuxt/nuxt PR: 29366
File: packages/nuxt/src/app/components/nuxt-root.vue:16-19
Timestamp: 2024-12-12T12:36:34.871Z
Learning: In `packages/nuxt/src/app/components/nuxt-root.vue`, when optimizing bundle size by conditionally importing components based on route metadata, prefer using inline conditional imports like:

```js
const IsolatedPage = route?.meta?.isolate ? defineAsyncComponent(() => import('#build/isolated-page.mjs')) : null
```

instead of wrapping the import in a computed property or importing the component unconditionally.
📚 Learning: 2025-07-18T16:46:07.446Z
Learnt from: CR
Repo: nuxt/nuxt PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T16:46:07.446Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Write unit tests for core functionality using `vitest`

Applied to files:

  • packages/nuxt/test/component-layer-priority.test.ts
📚 Learning: 2025-07-18T16:46:07.446Z
Learnt from: CR
Repo: nuxt/nuxt PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T16:46:07.446Z
Learning: Applies to **/e2e/**/*.{ts,js} : Write end-to-end tests using Playwright and `nuxt/test-utils`

Applied to files:

  • packages/nuxt/test/component-layer-priority.test.ts
📚 Learning: 2024-11-05T15:22:54.759Z
Learnt from: GalacticHypernova
Repo: nuxt/nuxt PR: 26468
File: packages/nuxt/src/components/plugins/loader.ts:24-24
Timestamp: 2024-11-05T15:22:54.759Z
Learning: In `packages/nuxt/src/components/plugins/loader.ts`, the references to `resolve` and `distDir` are legacy code from before Nuxt used the new unplugin VFS and will be removed.

Applied to files:

  • packages/nuxt/test/component-layer-priority.test.ts
📚 Learning: 2024-12-12T12:36:34.871Z
Learnt from: huang-julien
Repo: nuxt/nuxt PR: 29366
File: packages/nuxt/src/app/components/nuxt-root.vue:16-19
Timestamp: 2024-12-12T12:36:34.871Z
Learning: In `packages/nuxt/src/app/components/nuxt-root.vue`, when optimizing bundle size by conditionally importing components based on route metadata, prefer using inline conditional imports like:

```js
const IsolatedPage = route?.meta?.isolate ? defineAsyncComponent(() => import('#build/isolated-page.mjs')) : null
```

instead of wrapping the import in a computed property or importing the component unconditionally.

Applied to files:

  • packages/nuxt/test/component-layer-priority.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, js, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, vite, default, manifest-off, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, rspack, default, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, default, manifest-on, json, lts/-1)
  • GitHub Check: release-pkg-pr-new
  • GitHub Check: typecheck (ubuntu-latest, bundler)
  • GitHub Check: code
🔇 Additional comments (2)
packages/nuxt/test/component-layer-priority.test.ts (2)

1-9: LGTM!

The imports and fixture path setup are appropriate. Path normalization ensures cross-platform compatibility.


19-24: No changes needed—removeAllHooks() does not interfere with component directory resolution.

The test flow shows that the components:dirs hook listener is registered before buildNuxt() is called, and the hook fires during the module setup phase (before the builder.bundle override executes). The removeAllHooks() call happens inside the bundle function, which runs after the hook has already been processed. The test successfully captures and validates component directories with their priorities, confirming the hook fires correctly. The removeAllHooks() call is a test optimisation to skip post-bundling hooks, not a requirement for the test to function.

@danielroe danielroe merged commit 6b6ea5f into nuxt:main Nov 5, 2025
98 of 102 checks passed
@github-actions github-actions bot mentioned this pull request Nov 5, 2025
@github-actions github-actions bot mentioned this pull request Nov 6, 2025
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.

Layer priority override not working correctly in Nuxt 3.19.3 (works in 3.15.4)

3 participants