Skip to content

feat(validate): add 7 performance/web vitals validation rules#725

Merged
harlan-zw merged 2 commits into
mainfrom
feat/validate-perf-rules-2
Apr 7, 2026
Merged

feat(validate): add 7 performance/web vitals validation rules#725
harlan-zw merged 2 commits into
mainfrom
feat/validate-perf-rules-2

Conversation

@harlan-zw

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

Copy link
Copy Markdown
Collaborator

🔗 Linked issue

N/A

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

Adds 7 new validation rules to the ValidatePlugin focused on performance and Core Web Vitals anti-patterns:

Rule Severity What it catches
render-blocking-script warn <script src> in head without async/defer/type="module"
too-many-fetchpriority-high warn More than 2 (configurable) resources with fetchpriority="high" dilutes the signal
defer-on-module-script info Redundant defer on type="module" scripts
duplicate-resource-hint warn Same rel:href pair appearing multiple times in preload/prefetch/preconnect
charset-not-early warn <meta charset> not within first N (configurable, default 3) head tags
preload-not-modulepreload warn rel="preload" as="script" for a module script should use modulepreload
preconnect-missing-crossorigin warn Preconnect without crossorigin to an origin that serves CORS resources

too-many-fetchpriority-high and charset-not-early support configurable thresholds via the existing ESLint-style tuple syntax. All rules are documented and have full test coverage (20 new test cases).

Summary by CodeRabbit

  • New Features

    • Added seven performance-focused validation rules to detect render-blocking scripts, excessive high-priority resources, defer-on-module-script, duplicate resource hints, late charset declaration, preload vs modulepreload mismatches, and missing crossorigin on preconnect.
  • Documentation

    • Updated Validate plugin docs with the new rules and configuration examples for threshold options.
  • Tests

    • Expanded unit tests to cover the new rules and expected warning/no-warning behaviors.

@coderabbitai

coderabbitai Bot commented Apr 7, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c85cf72-9ebd-4571-9b86-ad7b2496b633

📥 Commits

Reviewing files that changed from the base of the PR and between 99e6afc and 34144c8.

📒 Files selected for processing (2)
  • packages/unhead/src/plugins/validate.ts
  • packages/unhead/test/unit/plugins/validate.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/unhead/test/unit/plugins/validate.test.ts
  • packages/unhead/src/plugins/validate.ts

📝 Walkthrough

Walkthrough

Adds seven performance-focused validation rules to the Validate plugin, updates documentation with their behavior and configuration examples, and adds unit tests covering warning and non-warning cases for each new rule.

Changes

Cohort / File(s) Summary
Documentation
docs/head/1.guides/plugins/validate.md
Expanded guide to document seven new performance/web-vitals rules (render-blocking-script, too-many-fetchpriority-high, defer-on-module-script, duplicate-resource-hint, charset-not-early, preload-not-modulepreload, preconnect-missing-crossorigin) and added configuration examples.
Plugin Implementation
packages/unhead/src/plugins/validate.ts
Extended ValidationRuleId and ValidationRuleOptions with the seven new rules; added extractOrigin() helper; implemented per-tag and cross-tag tags:afterResolve checks for render-blocking scripts, redundant defer on module scripts, fetchpriority-high counting, duplicate resource hints, charset position, preload→modulepreload mismatches, and preconnect crossorigin warnings; emits findings via existing report() flow.
Tests
packages/unhead/test/unit/plugins/validate.test.ts
Added unit tests asserting warning and non-warning behaviors for each new rule (render-blocking-script, defer-on-module-script, too-many-fetchpriority-high, duplicate-resource-hint, charset-not-early, preload-not-modulepreload, preconnect-missing-crossorigin).

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through head tags, keen and spry,

Found scripts that block and hints that vie,
I counted highs and squashed duplicates neat,
Nudged charset earlier and matched preload to modulebeat,
A twitch, a thump — now page loads fleet!

🚥 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 accurately reflects the main change: adding 7 new performance/web vitals validation rules to the validate plugin.
Description check ✅ Passed The description is comprehensive, including linked issue, type of change, and detailed information about the 7 new rules with a clear table and configuration details.

✏️ 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 feat/validate-perf-rules-2

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.

Add render-blocking-script, too-many-fetchpriority-high, defer-on-module-script,
duplicate-resource-hint, charset-not-early, preload-not-modulepreload, and
preconnect-missing-crossorigin rules to the ValidatePlugin.

These catch common performance anti-patterns that impact Core Web Vitals (LCP, INP, CLS)
including render-blocking scripts in head, diluted fetch priority signals, redundant
defer on modules, duplicate resource hints, late charset declarations, incorrect
preload vs modulepreload usage, and missing crossorigin on preconnect.
@harlan-zw harlan-zw force-pushed the feat/validate-perf-rules-2 branch from 3ed3893 to 99e6afc Compare April 7, 2026 06:17

@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)
packages/unhead/test/unit/plugins/validate.test.ts (1)

951-963: Test comment is technically inaccurate.

The comment states preconnects with and without crossorigin are "logically duplicates," but they establish different connection pools (non-CORS vs CORS). Both may be intentionally needed.

Consider updating the comment to acknowledge this is a design trade-off:

-      // Two preconnects to the same origin: one with crossorigin, one without.
-      // They hash differently so unhead keeps both, but they are logically duplicates.
+      // Two preconnects to the same origin: one with crossorigin, one without.
+      // While these technically serve different connection pools, having both
+      // is uncommon. The rule flags this as a potential oversight.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/unhead/test/unit/plugins/validate.test.ts` around lines 951 - 963,
Update the in-test comment in the test case that pushes two preconnect links
(the "warns on duplicate preconnect for same origin with different props"
it-block) to note that preconnects with and without crossorigin create different
connection pools (non-CORS vs CORS) and therefore may be intentionally distinct;
clarify this is a deliberate design trade-off related to the
'duplicate-resource-hint' validation rule and reference the relevant items (the
head.push entries, createValidationHead, and renderSSRHead) so future readers
understand why the test exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/unhead/src/plugins/validate.ts`:
- Around line 578-588: The duplicate-resource-hint detection currently uses only
rel:href as the dedupe key and thus flags legitimate preconnect pairs that
differ by crossorigin; update the logic in the loop (see resourceHintsSeen,
tags, report and tag.props.rel/tag.props.href) to treat preconnect specially by
incorporating tag.props.crossorigin into the key when tag.props.rel ===
'preconnect' (or alternatively skip preconnect entirely from this check); ensure
the chosen approach compares and stores keys accordingly so that non-CORS and
CORS preconnect links are not reported as duplicates while keeping the existing
behavior for preload/prefetch.

---

Nitpick comments:
In `@packages/unhead/test/unit/plugins/validate.test.ts`:
- Around line 951-963: Update the in-test comment in the test case that pushes
two preconnect links (the "warns on duplicate preconnect for same origin with
different props" it-block) to note that preconnects with and without crossorigin
create different connection pools (non-CORS vs CORS) and therefore may be
intentionally distinct; clarify this is a deliberate design trade-off related to
the 'duplicate-resource-hint' validation rule and reference the relevant items
(the head.push entries, createValidationHead, and renderSSRHead) so future
readers understand why the test exists.
🪄 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: 39549deb-bed6-421e-b2e1-8b40eacb58f4

📥 Commits

Reviewing files that changed from the base of the PR and between 8de2266 and 3ed3893.

📒 Files selected for processing (3)
  • docs/head/1.guides/plugins/validate.md
  • packages/unhead/src/plugins/validate.ts
  • packages/unhead/test/unit/plugins/validate.test.ts

Comment thread packages/unhead/src/plugins/validate.ts
@github-actions

github-actions Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Bundle Size Gzipped
Client (Minimal) 10.6 kB 4.3 kB
Server (Minimal) 10.3 kB 4.2 kB
Vue Client (Minimal) 11.6 kB 4.8 kB
Vue Server (Minimal) 11.3 kB 4.6 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

♻️ Duplicate comments (1)
packages/unhead/src/plugins/validate.ts (1)

616-626: ⚠️ Potential issue | 🟡 Minor

Preconnect deduplication key should account for crossorigin attribute.

The deduplication key ${rel}:${href} will flag legitimate preconnect pairs that differ by crossorigin as duplicates. Having both variants is intentional when an origin serves both CORS and non-CORS resources:

<link rel="preconnect" href="https://cdn.example.com">           <!-- For images -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin> <!-- For fonts -->

These establish separate connection pools in the browser.

,

Proposed fix: include crossorigin in key for preconnects
 for (const tag of tags) {
   if (tag.tag === 'link' && tag.props.href && (tag.props.rel === 'preload' || tag.props.rel === 'prefetch' || tag.props.rel === 'preconnect')) {
-    const key = `${tag.props.rel}:${tag.props.href}`
+    const crossoriginSuffix = tag.props.rel === 'preconnect' && 'crossorigin' in tag.props ? ':cors' : ''
+    const key = `${tag.props.rel}:${tag.props.href}${crossoriginSuffix}`
     if (resourceHintsSeen.has(key))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/unhead/src/plugins/validate.ts` around lines 616 - 626, The dedupe
key for resource hints in validate.ts currently uses `${rel}:${href}` and
incorrectly treats preconnects with different crossorigin attributes as
duplicates; update the logic in the loop that builds resourceHintsSeen
(referencing resourceHintsSeen, tag, tag.props.rel, tag.props.href,
tag.props.crossorigin) so that when tag.props.rel === 'preconnect' the key
includes the crossorigin value (e.g.
`${rel}:${href}:crossorigin=${tag.props.crossorigin}`) while keeping existing
keys for preload/prefetch unchanged, then use that key for the
resourceHintsSeen.has/set checks and duplicate reporting via
report('duplicate-resource-hint', ...).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/unhead/src/plugins/validate.ts`:
- Around line 657-672: The rule currently collects corsOrigins from any tag with
crossorigin and then warns on non-CORS preconnects, causing false positives when
a CORS preconnect already exists; fix by tracking origins of preconnect tags
that already include crossorigin (e.g., build a preconnectCorsOrigins Set from
tags where tag.tag==='link' && tag.props.rel==='preconnect' && 'crossorigin' in
tag.props using extractOrigin) and in the second loop skip reporting if the
origin is present in that preconnectCorsOrigins set (in addition to the existing
corsOrigins check), so a deliberate pair of preconnects (with and without
crossorigin) does not trigger preconnect-missing-crossorigin.

---

Duplicate comments:
In `@packages/unhead/src/plugins/validate.ts`:
- Around line 616-626: The dedupe key for resource hints in validate.ts
currently uses `${rel}:${href}` and incorrectly treats preconnects with
different crossorigin attributes as duplicates; update the logic in the loop
that builds resourceHintsSeen (referencing resourceHintsSeen, tag,
tag.props.rel, tag.props.href, tag.props.crossorigin) so that when tag.props.rel
=== 'preconnect' the key includes the crossorigin value (e.g.
`${rel}:${href}:crossorigin=${tag.props.crossorigin}`) while keeping existing
keys for preload/prefetch unchanged, then use that key for the
resourceHintsSeen.has/set checks and duplicate reporting via
report('duplicate-resource-hint', ...).
🪄 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: 13232884-4f35-4521-b744-f9d96e2f343c

📥 Commits

Reviewing files that changed from the base of the PR and between 3ed3893 and 99e6afc.

📒 Files selected for processing (3)
  • docs/head/1.guides/plugins/validate.md
  • packages/unhead/src/plugins/validate.ts
  • packages/unhead/test/unit/plugins/validate.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/unhead/test/unit/plugins/validate.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/head/1.guides/plugins/validate.md

Comment thread packages/unhead/src/plugins/validate.ts
Preconnects with and without crossorigin establish separate connection pools
and are intentionally paired. Include crossorigin in the dedupe key for
duplicate-resource-hint, and skip preconnect-missing-crossorigin when a CORS
preconnect already exists for the same origin.
@harlan-zw harlan-zw merged commit 093a277 into main Apr 7, 2026
6 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