chore(bench): add vue client seo bundle size and perf benchmarks#740
Conversation
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.
📝 WalkthroughWalkthroughThe 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Bundle Size Analysis
|
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
bench/bundle/src/vue-client/minimal-seo.tsbench/bundle/vue-client-seo-build.config.tsbench/bundle/vue-client-seo-plugin-build.config.tsbench/use-seo-meta-perf-transformed.bench.tspackages/bundler/src/unplugin/SSRStaticReplace.tspackages/bundler/test/ssrStaticReplace.test.ts
| description: 'Home page description', | ||
| image: 'https://nuxtjs.org/meta_0.png', | ||
| } | ||
| for (const i in Array.from({ length: 1000 })) { |
There was a problem hiding this comment.
🧩 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.tsRepository: 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.
| 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.
🔗 Linked issue
N/A
❓ Type of change
📚 Description
Adds benchmark configs to measure
useSeoMetabundle 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-transformeduseHeadcalls vs runtimeuseSeoMetaresolution.Summary by CodeRabbit
Tests
Chores