html-eslint v0.57.0

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

.eslintrc.js
module.exports = {
  rules: {
    "@html-eslint/head-order": "error",
  },
};

Options

This rule has an optional object option with the following property:

  1. ignores: An array of ignore pattern objects. Each object can have the following optional properties:
    1. tagPattern: Regex pattern for tag names (e.g., "^script$", "^link$")
    2. attrKeyPattern: Regex pattern for attribute keys (e.g., "^rel$", "^data-")
    3. 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.

.eslintrc.js
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):

  1. META (weight: 10) - Critical meta tags (<base>, <meta charset>, <meta name="viewport">, critical http-equiv meta tags)
  2. TITLE (weight: 9) - The <title> element
  3. PRECONNECT (weight: 8) - <link rel="preconnect">
  4. ASYNC_SCRIPT (weight: 7) - <script src="..." async>
  5. IMPORT_STYLES (weight: 6) - <style> elements with @import
  6. SYNC_SCRIPT (weight: 5) - Synchronous <script> elements (inline or blocking external scripts)
  7. SYNC_STYLES (weight: 4) - <link rel="stylesheet"> and inline <style> (without @import)
  8. PRELOAD (weight: 3) - <link rel="preload">
  9. DEFER_SCRIPT (weight: 2) - <script src="..." defer> and <script type="module">
  10. PREFETCH_PRERENDER (weight: 1) - <link rel="prefetch">, <link rel="dns-prefetch">, <link rel="prerender">
  11. 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:

.eslintrc.js
{
  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:

.eslintrc.js
{
  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:

.eslintrc.js
{
  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:

  1. Critical metadata (charset, viewport) should come first so the browser knows how to interpret the document
  2. Title should come early for accessibility and SEO
  3. Preconnect links should come before resources that use those connections
  4. Async scripts can start downloading early without blocking rendering
  5. Synchronous scripts and styles should be positioned to minimize render-blocking
  6. Preloads should come after render-critical resources
  7. 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).

Further Reading

  1. capo.js - Get your <head> in order
  2. CT.css - Let's take a look at your <head>
  3. Vitaly Friedman - Nordic.js 2022