head-order
Enforce optimal ordering of elements in
<head>
The order of elements in the <head> tag can affect the (perceived) performance of a web page. This rule enforces the optimal ordering of <head> elements based on capo.js.
Fixable: This rule is automatically fixable using the --fix flag on the command line.
How to use
module.exports = {
rules: {
"@html-eslint/head-order": "error",
},
};
Options
This rule has an optional object option with the following property:
ignores: An array of ignore pattern objects. Each object can have the following optional properties:tagPattern: Regex pattern for tag names (e.g.,"^script$","^link$")attrKeyPattern: Regex pattern for attribute keys (e.g.,"^rel$","^data-")attrValuePattern: Regex pattern for attribute values (e.g.,"analytics","^https://cdn")
All specified patterns within an ignore object must match (AND condition) for an element to be ignored.
module.exports = {
rules: {
"@html-eslint/head-order": [
"error",
{
ignores: [
// Ignore all script tags
{ tagPattern: "^script$" },
// Ignore link tags with rel="preconnect" containing "cdn" in the value
{
tagPattern: "^link$",
attrKeyPattern: "^rel$",
attrValuePattern: "cdn",
},
],
},
],
},
};
Rule Details
This rule enforces that elements within the <head> tag follow the optimal loading order for web performance. Elements are categorized and should appear in this order (from highest to lowest priority):
- META (weight: 10) - Critical meta tags (
<base>,<meta charset>,<meta name="viewport">, criticalhttp-equivmeta tags) - TITLE (weight: 9) - The
<title>element - PRECONNECT (weight: 8) -
<link rel="preconnect"> - ASYNC_SCRIPT (weight: 7) -
<script src="..." async> - IMPORT_STYLES (weight: 6) -
<style>elements with@import - SYNC_SCRIPT (weight: 5) - Synchronous
<script>elements (inline or blocking external scripts) - SYNC_STYLES (weight: 4) -
<link rel="stylesheet">and inline<style>(without@import) - PRELOAD (weight: 3) -
<link rel="preload"> - DEFER_SCRIPT (weight: 2) -
<script src="..." defer>and<script type="module"> - PREFETCH_PRERENDER (weight: 1) -
<link rel="prefetch">,<link rel="dns-prefetch">,<link rel="prerender"> - OTHER (weight: 0) - Everything else
Examples of incorrect code for this rule:
<!-- ✗ BAD: title should come before preconnect -->
<head>
<meta charset="UTF-8" />
<link rel="preconnect" href="https://example.com" />
<title>Page Title</title>
</head><!-- ✗ BAD: meta charset should come before title -->
<head>
<title>Page Title</title>
<meta charset="UTF-8" />
</head><!-- ✗ BAD: stylesheet should come before preload -->
<head>
<meta charset="UTF-8" />
<title>Page Title</title>
<link rel="preload" href="font.woff" as="font" />
<link rel="stylesheet" href="styles.css" />
</head>Examples of correct code for this rule:
<!-- ✓ GOOD: optimal order -->
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Page Title</title>
<link rel="preconnect" href="https://example.com" />
<script src="script.js" async></script>
<style>
@import url("styles.css");
</style>
<script>
console.log("sync script");
</script>
<link rel="stylesheet" href="styles.css" />
<link rel="preload" href="font.woff" as="font" />
<script src="deferred.js" defer></script>
<link rel="prefetch" href="next-page.html" />
</head><!-- ✓ GOOD: base comes before title (both are META category) -->
<head>
<base href="/" />
<meta charset="UTF-8" />
<title>Page Title</title>
</head>
With ignores option
ignores option">
Example 1: Ignore all script tags
<!-- ✓ GOOD: all scripts are ignored, other elements follow optimal order -->
<head>
<meta charset="UTF-8" />
<title>Page Title</title>
<link rel="stylesheet" href="styles.css" />
<script src="analytics.js" defer></script>
<!-- script position is ignored -->
<script src="app.js"></script>
<!-- script position is ignored -->
</head>With configuration:
{
ignores: [{ tagPattern: "^script$" }];
}
Example 2: Ignore scripts with specific src (AND condition)
<!-- ✓ GOOD: only analytics scripts are ignored -->
<head>
<meta charset="UTF-8" />
<title>Page Title</title>
<script src="async.js" async></script>
<!-- follows optimal order -->
<script src="analytics.js"></script>
<!-- ignored: matches both tagPattern AND attrValuePattern -->
</head>With configuration:
{
ignores: [{ tagPattern: "^script$", attrValuePattern: "analytics" }];
}
Example 3: Multiple ignore patterns
<!-- ✓ GOOD: analytics scripts and cdn preconnect links are ignored -->
<head>
<meta charset="UTF-8" />
<title>Page Title</title>
<link rel="stylesheet" href="styles.css" />
<script src="analytics.js"></script>
<!-- ignored -->
<link rel="preconnect" href="https://cdn.example.com" />
<!-- ignored -->
</head>With configuration:
{
ignores: [
{ tagPattern: "^script$", attrValuePattern: "analytics" },
{ tagPattern: "^link$", attrValuePattern: "cdn" },
];
}
Why?
The order of elements in the <head> affects how quickly a browser can start rendering a page and how soon users can interact with it:
- Critical metadata (
charset,viewport) should come first so the browser knows how to interpret the document - Title should come early for accessibility and SEO
- Preconnect links should come before resources that use those connections
- Async scripts can start downloading early without blocking rendering
- Synchronous scripts and styles should be positioned to minimize render-blocking
- Preloads should come after render-critical resources
- Deferred scripts and prefetch resources have lower priority
Following this order can improve Core Web Vitals metrics like First Contentful Paint (FCP) and Largest Contentful Paint (LCP).