Skip to content

chore(bench): add vue client seo bundle size and perf benchmarks#740

Merged
harlan-zw merged 2 commits into
mainfrom
bench/vue-client-seo-bundle
Apr 12, 2026
Merged

chore(bench): add vue client seo bundle size and perf benchmarks#740
harlan-zw merged 2 commits into
mainfrom
bench/vue-client-seo-bundle

Conversation

@harlan-zw

@harlan-zw harlan-zw commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator

🔗 Linked issue

N/A

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore

📚 Description

Adds benchmark configs to measure useSeoMeta bundle size with and without the Unhead Vite plugin transforms (UseSeoMetaTransform, SSRStaticReplace, TreeshakeServerComposables). Also includes a transformed variant of the SSR performance benchmark to compare runtime cost of pre-transformed useHead calls vs runtime useSeoMeta resolution.

Summary by CodeRabbit

  • Tests

    • Added comprehensive tests for SSR static replacement optimization, verifying correct behavior in both build and development environments.
  • Chores

    • Added performance benchmarking infrastructure to evaluate SEO metadata handling and bundling efficiency.
    • Added multiple build configurations to measure optimization impact across different scenarios.
    • Optimized SSR transformation behavior to skip processing during development mode, improving local development server performance.

In vite dev mode (serve), both SSR and client environments share the
same vite dev server. The SSRStaticReplace plugin was replacing `head.ssr`
with `false` for all environments, causing useHead to always use
clientUseHead instead of head.push during SSR renders.

This broke reactive ref resolution in SSR because clientUseHead uses
Vue watchers (client behavior) instead of synchronous push (SSR behavior).

The fix disables the static replacement in dev mode, preserving the
runtime `head.ssr` value so each environment behaves correctly.
Add build configs to measure useSeoMeta bundle size with and without the
Unhead Vite plugin transforms, plus a transformed variant of the SSR
performance benchmark.
@coderabbitai

coderabbitai Bot commented Apr 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR introduces SEO metadata bundling benchmarks with two build configurations (baseline and with bundler unplugins), adds a performance test for comparison, and modifies the SSRStaticReplace unplugin to skip transformations during Vite dev mode while preserving them for production builds.

Changes

Cohort / File(s) Summary
SEO Benchmark Infrastructure
bench/bundle/src/vue-client/minimal-seo.ts, bench/use-seo-meta-perf-transformed.bench.ts
New SEO metadata module with realistic configuration (Open Graph, Twitter cards) and corresponding performance benchmark measuring 1000 useHead calls with SSR rendering.
Build Configurations
bench/bundle/vue-client-seo-build.config.ts, bench/bundle/vue-client-seo-plugin-build.config.ts
Two Unbuild configurations: baseline bundler without unplugins and optimized variant with three bundler unplugins (UseSeoMetaTransform, TreeshakeServerComposables, SSRStaticReplace) for comparative benchmarking.
SSRStaticReplace Plugin
packages/bundler/src/unplugin/SSRStaticReplace.ts
Added enabled flag to gate transformInclude logic; plugin now detects dev serve mode and disables static replacement during Vite dev server, while preserving transforms for build passes.
SSRStaticReplace Tests
packages/bundler/test/ssrStaticReplace.test.ts
New test suite validating plugin behavior across build modes (client/SSR) and dev mode, ensuring transforms apply only during builds and correctly replace head.ssr with boolean literals.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Benchmarks hopping bright, comparing side-by-side,
SEO metadata set, with plugins as our guide.
Dev mode skips the dance, but builds transform with might,
Static replacements render perfect in the light.

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Vue client SEO bundle size and performance benchmarks, which aligns with all the files added in the changeset.
Description check ✅ Passed The description follows the template with the linked issue, type of change (chore), and a detailed explanation of what benchmarks were added and their purpose.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bench/vue-client-seo-bundle

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.

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Bundle Size Gzipped
Client (Minimal) 10.7 kB 4.4 kB
Server (Minimal) 10.6 kB 4.3 kB
Vue Client (Minimal) 11.8 kB 4.8 kB
Vue Server (Minimal) 11.6 kB 4.7 kB

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
bench/bundle/vue-client-seo-build.config.ts (1)

23-53: Extract shared benchmark build config to prevent drift.

The alias table, externals, and size-reporting logic are duplicated with the plugin config file. A shared base config/helper would reduce maintenance risk and keep both benchmark paths aligned.

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

In `@bench/bundle/vue-client-seo-build.config.ts` around lines 23 - 53, The alias
entries block (alias.entries), externals array, and the build:done
size-reporting hook are duplicated across this config and the plugin
config—extract these into a shared helper or base config module (e.g., export
sharedAliasEntries, sharedExternals, and a sharedBuildDoneHook function) and
import/compose them in both vue-client-seo-build.config.ts and the plugin
config; update this file to reference the sharedAliasEntries for alias.entries,
sharedExternals for externals, and replace the inline hooks['build:done'] logic
with the sharedBuildDoneHook to centralize maintenance and keep both benchmark
configs in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bench/use-seo-meta-perf-transformed.bench.ts`:
- Line 17: The benchmark loop currently uses "for (const i in Array.from({
length: 1000 }))", which creates allocations and iterates string keys; replace
it with an index-based loop that uses a numeric counter from 0 up to 999 (or
iterates up to the array length with a numeric index) to eliminate the
Array.from allocation and for...in string-key iteration and ensure the benchmark
measures only the target work.

---

Nitpick comments:
In `@bench/bundle/vue-client-seo-build.config.ts`:
- Around line 23-53: The alias entries block (alias.entries), externals array,
and the build:done size-reporting hook are duplicated across this config and the
plugin config—extract these into a shared helper or base config module (e.g.,
export sharedAliasEntries, sharedExternals, and a sharedBuildDoneHook function)
and import/compose them in both vue-client-seo-build.config.ts and the plugin
config; update this file to reference the sharedAliasEntries for alias.entries,
sharedExternals for externals, and replace the inline hooks['build:done'] logic
with the sharedBuildDoneHook to centralize maintenance and keep both benchmark
configs in sync.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e303d1bc-3c61-4195-93b5-56a5eedaa500

📥 Commits

Reviewing files that changed from the base of the PR and between 2d097ca and e526bc9.

📒 Files selected for processing (6)
  • bench/bundle/src/vue-client/minimal-seo.ts
  • bench/bundle/vue-client-seo-build.config.ts
  • bench/bundle/vue-client-seo-plugin-build.config.ts
  • bench/use-seo-meta-perf-transformed.bench.ts
  • packages/bundler/src/unplugin/SSRStaticReplace.ts
  • packages/bundler/test/ssrStaticReplace.test.ts

description: 'Home page description',
image: 'https://nuxtjs.org/meta_0.png',
}
for (const i in Array.from({ length: 1000 })) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify loop shapes used by SEO perf benchmarks.
rg -n -C2 --type=ts 'for\s*\(' bench/use-seo-meta-perf*.bench.ts
rg -n --type=ts 'for\s*\(const\s+\w+\s+in\s+Array\.from' bench/use-seo-meta-perf*.bench.ts

Repository: unjs/unhead

Length of output: 988


Use an index loop to avoid benchmark pollution from loop overhead.

for...in with Array.from(...) adds extra allocation overhead and iterates string keys, both of which distort the runtime measurements in your benchmark.

Proposed fix
-    for (const i in Array.from({ length: 1000 })) {
+    for (let i = 0; i < 1000; i++) {
       useHead(head, {
         title: `${page.title}-${i} | Nuxt`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const i in Array.from({ length: 1000 })) {
for (let i = 0; i < 1000; i++) {
useHead(head, {
title: `${page.title}-${i} | Nuxt`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bench/use-seo-meta-perf-transformed.bench.ts` at line 17, The benchmark loop
currently uses "for (const i in Array.from({ length: 1000 }))", which creates
allocations and iterates string keys; replace it with an index-based loop that
uses a numeric counter from 0 up to 999 (or iterates up to the array length with
a numeric index) to eliminate the Array.from allocation and for...in string-key
iteration and ensure the benchmark measures only the target work.

@harlan-zw harlan-zw merged commit 4c01273 into main Apr 12, 2026
8 checks passed
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.

1 participant