Skip to content

perf(Button): split link and plain button behavior#6273

Closed
J-Michalek wants to merge 1 commit intonuxt:v4from
J-Michalek:perf/button
Closed

perf(Button): split link and plain button behavior#6273
J-Michalek wants to merge 1 commit intonuxt:v4from
J-Michalek:perf/button

Conversation

@J-Michalek
Copy link
Copy Markdown
Contributor

@J-Michalek J-Michalek commented Mar 31, 2026

🔗 Linked issue

Resolves #4154

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

The link functionality gets pretty heavy when there are many buttons on the same page for example in a list of items. This does not help the case where there are many links on the page, but atleast the users that want basic buttons don't have the performance overload of the link.

Some meassurements before and after:
image

📝 Checklist

 

 

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@github-actions github-actions Bot added the v4 #4488 label Mar 31, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 31, 2026

npm i https://pkg.pr.new/@nuxt/ui@6273

commit: b0040f9

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 4, 2026

📝 Walkthrough

Walkthrough

This change optimizes the Button component's rendering behavior and introduces performance benchmarking. The Button component now explicitly disables automatic attribute inheritance via defineOptions({ inheritAttrs: false }) and uses a computed isLink property to conditionally render either a link or button element. Attribute forwarding has been refactored to explicitly merge $attrs with link properties instead of using an omit() function, and a separate rendering path for non-link buttons has been introduced. A new benchmark file tests mount performance of Button instances in various configurations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Rationale

The Button.vue changes involve conditional rendering logic with two distinct code paths (link vs. non-link), explicit attribute forwarding with special handling for certain props, and refactored binding patterns. While individual changes follow clear patterns, the interaction between attribute inheritance control, computed properties, and conditional rendering requires separate reasoning for each branch. The benchmark file is straightforward and repetitive in structure. Combined scope and logic density warrant moderate review effort.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'perf(Button): split link and plain button behavior' accurately and concisely describes the main change—separating link and plain button rendering to improve performance.
Linked Issues check ✅ Passed The changes directly address issue #4154 by implementing conditional rendering of link vs. plain button components, reducing performance overhead when rendering large numbers of buttons, with benchmarks demonstrating the expected improvements.
Out of Scope Changes check ✅ Passed All changes are directly within scope: Button.vue refactoring to split link/plain behavior and Button.bench.ts for performance validation of the optimization.
Description check ✅ Passed The PR description clearly relates to the changeset, explaining the performance optimization that splits link and plain button behavior to reduce overhead.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/components/Button.vue`:
- Around line 123-130: The link branch rendering of ULink is missing the
required data-slot attribute; update the ULink root element (the template branch
where isLink is true and ULink is used) to include data-slot="name" (matching
the naming used elsewhere) alongside the existing props (v-slot, v-bind, :type,
:disabled, custom) so the link branch has the same slot identification attribute
as the non-link root; ensure the attribute is added on the ULink tag (the
component used with v-slot="{ active, ...slotProps }") and preserved when
merging $attrs/omit(linkProps).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0f9201fd-994d-4e85-a357-153f6945ec8d

📥 Commits

Reviewing files that changed from the base of the PR and between ef5959f and b0040f9.

📒 Files selected for processing (2)
  • src/runtime/components/Button.vue
  • test/components/Button.bench.ts

Comment on lines 123 to 130
<ULink
v-if="isLink"
v-slot="{ active, ...slotProps }"
v-bind="{ ...$attrs, ...omit(linkProps, ['type', 'disabled', 'onClick']) }"
:type="type"
:disabled="disabled || isLoading"
v-bind="omit(linkProps, ['type', 'disabled', 'onClick'])"
custom
>
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.

⚠️ Potential issue | 🟡 Minor

Add data-slot on the link-branch root element.

Line 123 renders ULink without a data-slot attribute, while the non-link root already has one.

Proposed fix
   <ULink
+    data-slot="link"
     v-if="isLink"

As per coding guidelines: "Add data-slot="name" attributes on all template elements for slot identification".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Button.vue` around lines 123 - 130, The link branch
rendering of ULink is missing the required data-slot attribute; update the ULink
root element (the template branch where isLink is true and ULink is used) to
include data-slot="name" (matching the naming used elsewhere) alongside the
existing props (v-slot, v-bind, :type, :disabled, custom) so the link branch has
the same slot identification attribute as the non-link root; ensure the
attribute is added on the ULink tag (the component used with v-slot="{ active,
...slotProps }") and preserved when merging $attrs/omit(linkProps).

@benjamincanac
Copy link
Copy Markdown
Member

@J-Michalek This doesn't solve the core problem when rendering buttons with a link 😬

@J-Michalek
Copy link
Copy Markdown
Contributor Author

@benjamincanac Yes, I know, but I've no idea how to improve that at the moment. If I remember correctly NuxtLink itself was pretty heavy.

@benjamincanac
Copy link
Copy Markdown
Member

benjamincanac commented Apr 7, 2026

I don't know either but I ended up doing something similar at the Link level in #6310 to solve another issue, do you agree we can close this one in favor of #6310?

@J-Michalek
Copy link
Copy Markdown
Contributor Author

@benjamincanac I did some more benchmarking and I think this PR or changes related to this PR should be applied:
image

#6310 doesn't seem to provide much help atleast on initial render, I think it helps with the dep tracking issues.

@benjamincanac
Copy link
Copy Markdown
Member

@J-Michalek What do you mean? It does exactly the same thing at the Link level 🤔 https://github.com/nuxt/ui/pull/6310/changes#diff-c3072f78bc4ba4ddd2eaf378a34f73548386783dfdde8fa0dce9585c27e61fe6R207

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance Issue Rendering Large List of UButtons – flushJobs Extremely Slow

2 participants