You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* feat(validate): add 7 performance/web vitals validation rules
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.
* fix(validate): handle dual preconnect pattern (CORS + non-CORS)
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.
Copy file name to clipboardExpand all lines: docs/head/1.guides/plugins/validate.md
+10-1Lines changed: 10 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
---
2
2
title: "Validate"
3
-
description: "Catch common SEOand head tag mistakes. Validates URLs, meta tags, Open Graph, Twitter Cards, and detects typos with fuzzy matching."
3
+
description: "Catch common SEO, performance, and head tag mistakes. Validates URLs, meta tags, Open Graph, Twitter Cards, web vitals anti-patterns, and detects typos with fuzzy matching."
4
4
navigation.title: "Validate"
5
5
---
6
6
@@ -130,6 +130,13 @@ Rules inspired by [webperf-snippets](https://webperf-snippets.nucliweb.net/) tha
130
130
131
131
| Rule ID | Severity | What it catches |
132
132
|---------|----------|----------------|
133
+
|`render-blocking-script`|`warn`|`<script src>` in head without `async`, `defer`, or `type="module"` blocks the critical rendering path |
134
+
|`too-many-fetchpriority-high`|`warn`| More than 2 resources with `fetchpriority="high"`. When everything is high priority, nothing is |
135
+
|`defer-on-module-script`|`info`|`defer` on a `type="module"` script is redundant. Modules are deferred by default |
136
+
|`duplicate-resource-hint`|`warn`| Same `rel`/`href` pair appears multiple times in preload, prefetch, or preconnect tags |
137
+
|`charset-not-early`|`warn`|`<meta charset>` is not within the first few tags in `<head>`, which can force the browser to re-parse |
138
+
|`preload-not-modulepreload`|`warn`|`<link rel="preload" as="script">` for a module script should use `rel="modulepreload"` to also trigger module parsing |
139
+
|`preconnect-missing-crossorigin`|`warn`|`<link rel="preconnect">` is missing `crossorigin` but CORS resources are loaded from that origin, causing a separate connection |
133
140
|`preload-fetchpriority-conflict`|`warn`|`<link rel="preload" fetchpriority="low">` is contradictory — preload signals critical, low priority contradicts that |
134
141
|`too-many-preloads`|`warn`| More than 6 `<link rel="preload">` tags compete for bandwidth and hurt performance |
135
142
|`too-many-preconnects`|`warn`| More than 4 `<link rel="preconnect">` tags — each initiates a TCP+TLS handshake, competing for limited connections |
report('render-blocking-script',`Script "${props.src}" is render-blocking. Add "async", "defer", or use type="module" to avoid blocking the critical rendering path.`,'warn',tag)
report('too-many-fetchpriority-high',`Found ${highPriorityCount} resources with fetchpriority="high". When everything is high priority, nothing is. Limit to ${maxHighPriority} for the signal to be effective.`,'warn')
615
+
616
+
// Duplicate resource hints (same href in multiple preload/preconnect/prefetch)
report('charset-not-early',`<meta charset> is at position ${charsetPosition} in <head>. It should be within the first ${charsetMaxPos} tags so the browser doesn't need to re-parse.`,'warn',charsetTag)
646
+
647
+
// preload as="script" when the actual script is type="module" should use modulepreload
report('preload-not-modulepreload',`"${tag.props.href}" is a module script but uses rel="preload". Use rel="modulepreload" instead to also trigger module parsing.`,'warn',tag)
656
+
}
657
+
658
+
// Preconnect missing crossorigin for origins that serve CORS resources
report('preconnect-missing-crossorigin',`Preconnect to "${tag.props.href}" is missing "crossorigin" but CORS resources are loaded from this origin. Without it, the browser opens a separate connection for CORS requests.`,'warn',tag)
677
+
}
678
+
}
679
+
586
680
// Meta tags rendered after the crawler byte limit (default 1MB)
587
681
// Social crawlers (Facebook, Twitter) only parse the first ~1MB of HTML.
588
682
// Large inline styles can push SEO/OG meta tags past this limit.
0 commit comments