Loading
Get Your Head in Order

Get your <head> in order

How you order elements in the <head> can have an effect on the (perceived) performance of the page.

This snippet code it's from capo.js (opens in a new tab) the Rick Viscomi (opens in a new tab) script

Snippet

const ElementWeights = {
  META: 10,
  TITLE: 9,
  PRECONNECT: 8,
  ASYNC_SCRIPT: 7,
  IMPORT_STYLES: 6,
  SYNC_SCRIPT: 5,
  SYNC_STYLES: 4,
  PRELOAD: 3,
  DEFER_SCRIPT: 2,
  PREFETCH_PRERENDER: 1,
  OTHER: 0
};
 
const ElementDetectors = {
  META: isMeta,
  TITLE: isTitle,
  PRECONNECT: isPreconnect,
  ASYNC_SCRIPT: isAsyncScript,
  IMPORT_STYLES: isImportStyles,
  SYNC_SCRIPT: isSyncScript,
  SYNC_STYLES: isSyncStyles,
  PRELOAD: isPreload,
  DEFER_SCRIPT: isDeferScript,
  PREFETCH_PRERENDER: isPrefetchPrerender
}
 
const WEIGHT_COLORS = [
  '#9e0142',
  '#d53e4f',
  '#f46d43',
  '#fdae61',
  '#fee08b',
  '#e6f598',
  '#abdda4',
  '#66c2a5',
  '#3288bd',
  '#5e4fa2',
  '#cccccc'
];
 
const LOGGING_PREFIX = 'Capo: ';
 
function isMeta(element) {
  return element.matches('meta:is([charset], [http-equiv], [name=viewport])');
}
 
function isTitle(element) {
  return element.matches('title');
}
 
function isPreconnect(element) {
  return element.matches('link[rel=preconnect]');
}
 
function isAsyncScript(element) {
  return element.matches('script[src][async]');
}
 
function isImportStyles(element) {
  const importRe = /@import/;
 
  if (element.matches('style')) {
    return importRe.test(element.textContent);
  }
  return false;
}
 
function isSyncScript(element) {
  return element.matches('script:not([src][defer],[src][async],[type*=json])')
}
 
function isSyncStyles(element) {
  return element.matches('link[rel=stylesheet],style');
}
 
function isPreload(element) {
  return element.matches('link[rel=preload]');
}
 
function isDeferScript(element) {
  return element.matches('script[src][defer]');
}
 
function isPrefetchPrerender(element) {
  return element.matches('link:is([rel=prefetch], [rel=dns-prefetch], [rel=prerender])');
}
 
function getWeight(element) {
  for ([id, detector] of Object.entries(ElementDetectors)) {
    if (detector(element)) {
      return ElementWeights[id];
    }
  }
 
  return ElementWeights.OTHER;
}
 
function getHeadWeights() {
  const headChildren = Array.from(document.head.children);
  return headChildren.map(element => {
    return [element, getWeight(element)];
  });
}
 
function visualizeWeights(weights) {
  const visual = weights.map(_ => '%c ').join('');
  const styles = weights.map(weight => {
    const color = WEIGHT_COLORS[10 - weight];
    return `background-color: ${color}; padding: 5px; margin: -1px;`
  });
 
  return {visual, styles};
}
 
function visualizeWeight(weight) {
  const visual = `%c${new Array(weight + 1).fill('█').join('')}`;
  const style = `color: ${WEIGHT_COLORS[10 - weight]}`;
 
  return {visual, style};
}
 
function logWeights() {
  const headWeights = getHeadWeights();
  const actualViz = visualizeWeights(headWeights.map(([_, weight]) => weight));
  
  console.groupCollapsed(`${LOGGING_PREFIX}Actual %c<head>%c order\n${actualViz.visual}`, 'font-family: monospace', 'font-family: inherit',  ...actualViz.styles);
  headWeights.forEach(([element, weight]) => {
    const viz = visualizeWeight(weight);
    console.log(viz.visual, viz.style, weight + 1, element);
  });
  console.log('Actual %c<head>%c element', 'font-family: monospace', 'font-family: inherit', document.head);
  console.groupEnd();
 
  const sortedWeights = headWeights.sort((a, b) => {
    return b[1] - a[1];
  });
  const sortedViz = visualizeWeights(sortedWeights.map(([_, weight]) => weight));
  
  console.groupCollapsed(`${LOGGING_PREFIX}Sorted %c<head>%c order\n${sortedViz.visual}`, 'font-family: monospace', 'font-family: inherit', ...sortedViz.styles);
  const sortedHead = document.createElement('head');
  sortedWeights.forEach(([element, weight]) => {
    const viz = visualizeWeight(weight);
    console.log(viz.visual, viz.style, weight + 1, element);
    sortedHead.appendChild(element.cloneNode(true));
  });
  console.log('Sorted %c<head>%c element', 'font-family: monospace', 'font-family: inherit', sortedHead);
  console.groupEnd();
}
 
logWeights();