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
Typo detection only runs for recognized prefixes (`og:`, `article:`, `book:`, `profile:`, `fb:`, `twitter:`, or standard meta names without a colon). Custom prefixes like `custom:foo` are ignored.
120
120
121
+
### Performance Hints
122
+
123
+
Rules inspired by [webperf-snippets](https://webperf-snippets.nucliweb.net/) that catch common performance anti-patterns in head tags:
124
+
125
+
| Rule ID | Severity | What it catches |
126
+
|---------|----------|----------------|
127
+
|`preload-fetchpriority-conflict`|`warn`|`<link rel="preload" fetchpriority="low">` is contradictory — preload signals critical, low priority contradicts that |
128
+
|`too-many-preloads`|`warn`| More than 6 `<link rel="preload">` tags compete for bandwidth and hurt performance |
129
+
|`too-many-preconnects`|`warn`| More than 4 `<link rel="preconnect">` tags — each initiates a TCP+TLS handshake, competing for limited connections |
130
+
|`redundant-dns-prefetch`|`info`| Same origin has both `<link rel="preconnect">` and `<link rel="dns-prefetch">` — preconnect already includes DNS resolution |
131
+
|`preload-async-defer-conflict`|`warn`| A script is preloaded but also has `async` or `defer` — preload escalates the priority, defeating the purpose |
132
+
|`prefetch-preload-conflict`|`warn`| Same resource has both `preload` and `prefetch` — use preload for current page, prefetch for future navigation |
133
+
|`inline-style-size`|`info`| Inline `<style>` exceeds 14KB (the critical CSS budget for the first TCP round-trip) |
134
+
|`inline-script-size`|`info`| Inline `<script>` exceeds 2KB — consider moving to an external file for cacheability |
135
+
121
136
## How Do I Configure Rules?
122
137
123
138
Rules can be disabled or have their severity overridden, similar to ESLint's flat config:
@@ -134,6 +149,23 @@ ValidatePlugin({
134
149
```
135
150
::
136
151
152
+
Some rules accept an options object as an ESLint-style `[severity, options]` tuple:
153
+
154
+
::code-block
155
+
```ts [Input]
156
+
ValidatePlugin({
157
+
rules: {
158
+
'too-many-preloads': ['warn', { max: 10 }],
159
+
'too-many-preconnects': ['warn', { max: 6 }],
160
+
'inline-style-size': ['info', { maxKB: 20 }],
161
+
'inline-script-size': ['info', { maxKB: 5 }],
162
+
}
163
+
})
164
+
```
165
+
::
166
+
167
+
The configuration is fully type-safe — only rules that support options accept the tuple form, and options are typed per-rule.
168
+
137
169
## How Does Source Tracing Work?
138
170
139
171
Each validation rule includes a `source` field pointing to the `head.push()` call that introduced the problematic tag. By default this is an absolute path. Set `root` to get clickable relative paths in your terminal or IDE:
report('inline-style-size',`Inline <style> is ${sizeKB.toFixed(1)}KB — exceeds ${styleMaxKB}KB critical CSS budget. Consider moving to an external stylesheet for cacheability.`,'info',tag)
report('too-many-preloads',`Found ${preloadCount} preload links — more than ${maxPreloads} preloads compete for bandwidth and can hurt performance.`,'warn')
report('too-many-preconnects',`Found ${preconnectCount} preconnect links — each initiates a TCP+TLS handshake, more than ${maxPreconnects} compete for limited connections.`,'warn')
501
+
502
+
// Redundant dns-prefetch when preconnect exists for same origin
503
+
constpreconnectOrigins=newSet<string>()
504
+
constdnsPrefetchTags: HeadTag[]=[]
505
+
for(consttagoftags){
506
+
if(tag.tag==='link'&&tag.props.href){
507
+
if(tag.props.rel==='preconnect')
508
+
preconnectOrigins.add(tag.props.href)
509
+
elseif(tag.props.rel==='dns-prefetch')
510
+
dnsPrefetchTags.push(tag)
511
+
}
512
+
}
513
+
for(consttagofdnsPrefetchTags){
514
+
if(preconnectOrigins.has(tag.props.href))
515
+
report('redundant-dns-prefetch',`dns-prefetch for "${tag.props.href}" is redundant — preconnect already includes DNS resolution.`,'info',tag)
report('preload-async-defer-conflict',`Script "${tag.props.src}" is preloaded but has "${attr}" — preload escalates priority, defeating the purpose of ${attr}. Remove the preload or add fetchpriority="low" to the script.`,'warn',preloadTag)
530
+
}
531
+
}
532
+
}
533
+
534
+
// Prefetch + preload conflict (should be one or the other)
report('prefetch-preload-conflict',`"${tag.props.href}" has both preload and prefetch — use preload for current page resources, prefetch for future navigation.`,'warn',tag)
0 commit comments