Technical

Speed Index

A performance metric that measures how quickly the contents of a page are visibly populated, calculated as the average time at which visible parts of the page are displayed.

Quick Answer

  • What it is: A performance metric that measures how quickly the contents of a page are visibly populated, calculated as the average time at which visible parts of the page are displayed.
  • Why it matters: Speed Index captures the visual loading experience better than single-point metrics by measuring progressive rendering.
  • How to check or improve: Optimize resource loading order, implement progressive rendering, and prioritize above-the-fold content for Speed Index under 3.4s.

When you'd use this

Speed Index captures the visual loading experience better than single-point metrics by measuring progressive rendering.

Example scenario

Hypothetical scenario (not a real company)

A team might use Speed Index when Optimize resource loading order, implement progressive rendering, and prioritize above-the-fold content for Speed Index under 3.4s.

Common mistakes

  • Confusing Speed Index with First Contentful Paint (FCP): A performance metric that measures the time from when a page starts loading to when any part of the page's content is rendered on the screen.
  • Confusing Speed Index with Largest Contentful Paint (LCP): A Core Web Vitals metric that measures the render time of the largest image or text block visible within the viewport, relative to when the page first started loading.

How to measure or implement

  • Optimize resource loading order, implement progressive rendering, and prioritize above-the-fold content for Speed Index under 3
  • 4s

Analyze your visual loading speed

Start here
Updated Jan 20, 2026·4 min read

Speed Index is the cinematographer of web performance metrics—it doesn't just capture a single moment but the entire visual story of how your page loads. By measuring the progressive visual completeness of a page over time, Speed Index provides a nuanced view of perceived performance that single-point metrics like FCP or LCP miss. It answers the critical question: "How fast does the page look like it's loading?"

What is Speed Index?

Speed Index measures the average time at which visible parts of the page are displayed during load. It's calculated by capturing video of the page loading and computing the visual progression between frames. The result is expressed in milliseconds, with lower values indicating faster visual loading.

The calculation works by:

  1. Recording page load as video (typically at 10fps)
  2. Calculating visual completeness percentage for each frame
  3. Computing the area above the visual progress curve
  4. Expressing result in milliseconds

Mathematical formula:

Speed Index = Σ (1 - VC(t)) * interval

Where VC(t) is visual completeness at time t

Lighthouse Speed Index thresholds:

  • Good: 3.4 seconds or less
  • Needs Improvement: 3.4 to 5.8 seconds
  • Poor: Over 5.8 seconds

Why Speed Index Matters

Beyond Single-Point Metrics

Traditional metrics capture specific moments:

  • FCP: When first content appears
  • LCP: When largest content appears
  • TTI: When page becomes interactive

Speed Index captures the entire journey:

Visual Progress Comparison:

Site A (FCP=1s, LCP=3s, SI=2s):
100% ████████████████████
75%  ████████████
50%  ██████
25%  ███
0%   |-------|-------|
     0       2s      4s

Site B (FCP=0.5s, LCP=3s, SI=3.5s):
100% ████████████████████
75%                ████
50%                ██
25%  ██
0%   |-------|-------|
     0       2s      4s

Site A has better Speed Index despite slower FCP

User Perception Correlation

Speed Index strongly correlates with user satisfaction because it measures what users actually see:

  • Visual feedback loop: Users perceive progress when pixels change
  • Psychological anchoring: Early visual progress sets expectations
  • Completion perception: Gradual filling feels faster than sudden appearance

Studies show:

  • 1-second Speed Index improvement increases engagement by 5%
  • Sites with SI under 3s have 20% lower bounce rates
  • Progressive rendering (good SI) improves perceived performance by 30%

Business Impact

Companies optimizing Speed Index report:

Zillow: Reduced SI by 1.2s

  • 7% increase in conversion rate
  • 4% reduction in bounce rate

Pinterest: Improved SI by 40%

  • 15% increase in SEO traffic
  • 10% increase in signup conversion

Financial Times: Achieved 2.8s Speed Index

  • 23% increase in user engagement
  • 7% increase in article reads

How Speed Index Works

Visual Completeness Calculation

Speed Index uses computer vision to determine visual completeness:

// Simplified visual completeness algorithm
function calculateVisualCompleteness(frame, finalFrame) {
  let matchingPixels = 0
  let totalPixels = frame.width * frame.height

  for (let i = 0; i < totalPixels; i++) {
    if (frame.pixels[i] === finalFrame.pixels[i]) {
      matchingPixels++
    }
  }

  return matchingPixels / totalPixels // 0 to 1
}

// Calculate Speed Index
function calculateSpeedIndex(frames, interval = 100) {
  let speedIndex = 0
  let lastCompleteness = 0

  frames.forEach((frame, index) => {
    const completeness = calculateVisualCompleteness(
      frame,
      frames[frames.length - 1]
    )
    const incomplete = 1 - (lastCompleteness + completeness) / 2
    speedIndex += incomplete * interval
    lastCompleteness = completeness
  })

  return speedIndex
}

Viewport Considerations

Speed Index only measures the visible viewport:

  • Content below the fold doesn't affect SI
  • Mobile viewport typically 360x640px
  • Desktop viewport typically 1366x768px
  • Responsive design impacts SI differently per breakpoint

Perceptual Speed Index

A variant that considers human visual perception:

// Perceptual Speed Index weights changes by visual importance
function perceptualSpeedIndex(frames) {
  // Apply SSIM (Structural Similarity Index)
  // Considers luminance, contrast, and structure
  // More accurate for human perception

  return frames.reduce((psi, frame, i) => {
    const ssim = calculateSSIM(frame, finalFrame)
    const weight = getVisualWeight(frame) // Center-weighted
    return psi + (1 - ssim) * weight * interval
  }, 0)
}

Best Practices for Optimizing Speed Index

1. Implement Progressive Rendering

Critical CSS Inlining

<!DOCTYPE html>
<html>
  <head>
    <!-- Inline critical CSS for immediate render -->
    <style>
      /* Critical above-the-fold styles */
      body {
        margin: 0;
        font-family: system-ui;
      }
      .header {
        height: 60px;
        background: #333;
        color: white;
      }
      .hero {
        min-height: 400px;
        background: linear-gradient(#f0f0f0, #e0e0e0);
      }
      .content {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
      }

      /* Skeleton screen styles */
      .skeleton {
        background: linear-gradient(
          90deg,
          #f0f0f0 25%,
          #e0e0e0 50%,
          #f0f0f0 75%
        );
        background-size: 200% 100%;
        animation: loading 1.5s infinite;
      }
      @keyframes loading {
        0% {
          background-position: 200% 0;
        }
        100% {
          background-position: -200% 0;
        }
      }
    </style>

    <!-- Preload key resources -->
    <link rel="preload" href="/fonts/main.woff2" as="font" crossorigin />
    <link rel="preload" href="/css/main.css" as="style" />
    <link rel="preload" href="/img/hero.webp" as="image" />
  </head>
  <body>
    <!-- Immediate skeleton -->
    <header class="header skeleton"></header>
    <div class="hero skeleton"></div>
    <main class="content">
      <div class="skeleton" style="height: 20px; margin-bottom: 10px;"></div>
      <div class="skeleton" style="height: 20px; width: 80%;"></div>
    </main>

    <!-- Load full styles asynchronously -->
    <link
      rel="stylesheet"
      href="/css/main.css"
      media="print"
      onload="this.media='all'"
    />
  </body>
</html>

Progressive Image Loading

// Low-quality image preview (LQIP) technique
class ProgressiveImage {
  constructor(element) {
    this.element = element;
    this.preview = element.dataset.preview;
    this.src = element.dataset.src;

    this.loadImage();
  }

  loadImage() {
    // Step 1: Show tiny preview (inline base64)
    this.element.src = this.preview;
    this.element.classList.add('loading');

    // Step 2: Load full image
    const img = new Image();
    img.src = this.src;

    img.onload = () => {
      // Step 3: Fade in full image
      this.element.src = this.src;
      this.element.classList.remove('loading');
    };
  }
}

// CSS for smooth transition
.progressive-image {
  filter: blur(5px);
  transition: filter 0.3s;
}

.progressive-image.loading {
  filter: blur(20px);
}

2. Optimize Resource Loading Order

Resource Hints

<!-- DNS prefetch for external domains -->
<link rel="dns-prefetch" href="//cdn.example.com" />
<link rel="dns-prefetch" href="//fonts.googleapis.com" />

<!-- Preconnect for critical third-party origins -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin />

<!-- Preload critical resources -->
<link rel="preload" as="style" href="/css/critical.css" />
<link rel="preload" as="font" href="/fonts/main.woff2" crossorigin />
<link rel="preload" as="image" href="/img/hero.webp" />
<link rel="preload" as="script" href="/js/critical.js" />

<!-- Prefetch next page resources -->
<link rel="prefetch" href="/next-page.html" />
<link rel="prefetch" href="/css/next-page.css" />

Priority Hints

<!-- Fetch priority for key images -->
<img src="hero.jpg" fetchpriority="high" alt="Hero" />
<img src="below-fold.jpg" fetchpriority="low" loading="lazy" alt="Secondary" />

<!-- Script priorities -->
<script src="critical.js" fetchpriority="high"></script>
<script src="analytics.js" fetchpriority="low" async></script>

3. Implement Smart Loading Strategies

Adaptive Loading Based on Connection

// Adapt content based on network speed
class AdaptiveLoader {
  constructor() {
    this.connection =
      navigator.connection ||
      navigator.mozConnection ||
      navigator.webkitConnection
    this.effectiveType = this.connection?.effectiveType || "4g"
  }

  loadImages() {
    const images = document.querySelectorAll("[data-adaptive]")

    images.forEach(img => {
      switch (this.effectiveType) {
        case "slow-2g":
        case "2g":
          // Load only preview
          img.src = img.dataset.preview
          break
        case "3g":
          // Load medium quality
          img.src = img.dataset.medium || img.dataset.src
          break
        case "4g":
        default:
          // Load full quality
          img.src = img.dataset.src
      }
    })
  }

  loadVideos() {
    if (this.effectiveType === "slow-2g" || this.effectiveType === "2g") {
      // Don't autoload videos on slow connections
      document.querySelectorAll("video[autoplay]").forEach(video => {
        video.removeAttribute("autoplay")
        video.setAttribute("poster", video.dataset.poster)
      })
    }
  }
}

4. Use Service Workers for Instant Loading

App Shell Pattern

// sw.js - Cache app shell for instant Speed Index
const CACHE_NAME = "app-shell-v1"
const shellFiles = ["/", "/css/shell.css", "/js/shell.js", "/img/logo.svg"]

self.addEventListener("install", e => {
  e.waitUntil(caches.open(CACHE_NAME).then(cache => cache.addAll(shellFiles)))
})

self.addEventListener("fetch", e => {
  e.respondWith(
    caches.match(e.request).then(response => {
      // Return cached shell instantly (Speed Index ~0ms for shell)
      if (response) return response

      // Fetch dynamic content
      return fetch(e.request).then(response => {
        // Cache successful responses
        if (!response || response.status !== 200) return response

        const responseClone = response.clone()
        caches.open(CACHE_NAME).then(cache => {
          cache.put(e.request, responseClone)
        })

        return response
      })
    })
  )
})

5. Optimize Font Loading

Font Loading Strategies

/* Strategy 1: Optional font loading for best Speed Index */
@font-face {
  font-family: "Brand";
  src: url("/fonts/brand.woff2") format("woff2");
  font-display: optional; /* Use only if cached */
}

/* Strategy 2: Swap for important text */
@font-face {
  font-family: "Body";
  src: url("/fonts/body.woff2") format("woff2");
  font-display: swap; /* Show fallback immediately */
}

/* Strategy 3: Preload + swap for critical fonts */
/* In HTML: <link rel="preload" href="/fonts/heading.woff2" as="font" crossorigin> */
@font-face {
  font-family: "Heading";
  src: url("/fonts/heading.woff2") format("woff2");
  font-display: swap;
}

Font Loading API

// Progressive font loading
if ("fonts" in document) {
  // Load fonts programmatically
  const fontFace = new FontFace("Brand", "url(/fonts/brand.woff2)")

  fontFace.load().then(font => {
    document.fonts.add(font)
    document.body.classList.add("fonts-loaded")
  })

  // Or wait for all fonts
  document.fonts.ready.then(() => {
    document.body.classList.add("all-fonts-loaded")
  })
}

Common Speed Index Issues

Issue 1: Late-Loading Hero Images

Problem: Large hero images delaying visual progress

Solution: Progressive loading with previews

<!-- Inline SVG preview -->
<div
  class="hero"
  style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjYwMCI+CiAgPHJlY3Qgd2lkdGg9IjEyMDAiIGhlaWdodD0iNjAwIiBmaWxsPSIjZTBl')"
>
  >
  <img
    class="hero-image"
    src="hero-lowres.jpg"
    data-src="hero-fullres.jpg"
    loading="eager"
    alt="Hero"
  />
</div>

<script>
  // Load high-res when ready
  const hero = document.querySelector(".hero-image")
  const highRes = new Image()
  highRes.src = hero.dataset.src
  highRes.onload = () => {
    hero.src = highRes.src
    hero.classList.add("loaded")
  }
</script>

Issue 2: Flash of Unstyled Content (FOUC)

Problem: Content appears unstyled, then jumps when CSS loads

Solution: Critical CSS + proper loading order

// Detect and prevent FOUC
document.documentElement.className = "no-fouc"

// Critical CSS prevents FOUC
const criticalCSS = `
  .no-fouc { visibility: hidden; opacity: 0; }
  .fonts-loaded { visibility: visible; opacity: 1; transition: opacity 0.3s; }
`

// Inject critical CSS
const style = document.createElement("style")
style.textContent = criticalCSS
document.head.appendChild(style)

// Show content when ready
window.addEventListener("load", () => {
  document.documentElement.classList.add("fonts-loaded")
})

Issue 3: Slow Third-Party Content

Problem: Ads and widgets delaying visual completion

Solution: Load third-party content after visual completion

// Delay third-party content until after Speed Index window
const loadThirdParty = () => {
  // Load only after main content is visible
  if (document.readyState === "complete") {
    loadAds()
    loadSocialWidgets()
    loadAnalytics()
  }
}

// Wait for visual completion
if ("requestIdleCallback" in window) {
  requestIdleCallback(loadThirdParty, { timeout: 3000 })
} else {
  setTimeout(loadThirdParty, 3000)
}

Tools for Measuring Speed Index

WebPageTest

WebPageTest provides the most detailed Speed Index analysis:

  • Frame-by-frame visual progress
  • Filmstrip view
  • Speed Index calculation
  • Perceptual Speed Index

Lighthouse

# Measure Speed Index with Lighthouse
lighthouse https://example.com --only-audits=speed-index

# Detailed performance metrics
lighthouse https://example.com \
  --only-categories=performance \
  --output=json \
  --output-path=./report.json

# Extract Speed Index
cat report.json | jq '.audits["speed-index"].numericValue'

Custom Speed Index Calculation

// Simplified Speed Index calculation for monitoring
class SpeedIndexMonitor {
  constructor() {
    this.frames = []
    this.startTime = performance.now()
  }

  captureFrame() {
    // Capture visual state (simplified)
    const visibleElements = document.querySelectorAll(
      "*:not(script):not(style)"
    )
    const frame = {
      time: performance.now() - this.startTime,
      elements: visibleElements.length,
      images: document.querySelectorAll('img[src]:not([src=""])').length,
      text: document.body.innerText.length
    }
    this.frames.push(frame)
  }

  calculate() {
    if (this.frames.length < 2) return 0

    const lastFrame = this.frames[this.frames.length - 1]
    let speedIndex = 0

    for (let i = 1; i < this.frames.length; i++) {
      const frame = this.frames[i]
      const prevFrame = this.frames[i - 1]

      // Calculate completeness (simplified)
      const completeness =
        (frame.elements / lastFrame.elements) * 0.3 +
        (frame.images / Math.max(1, lastFrame.images)) * 0.4 +
        (frame.text / Math.max(1, lastFrame.text)) * 0.3

      const interval = frame.time - prevFrame.time
      speedIndex += (1 - completeness) * interval
    }

    return speedIndex
  }
}

Frequently Asked Questions

How is Speed Index different from other metrics?

Speed Index measures the entire visual loading progress, not just specific moments. While FCP marks first paint and LCP marks largest paint, Speed Index captures everything in between, providing a holistic view of visual performance.

Why is my Speed Index different between tools?

Different tools may:

  • Use different viewport sizes
  • Apply different visual comparison algorithms
  • Include/exclude certain page elements
  • Use different sampling rates (fps)

WebPageTest is generally considered the most accurate.

Can animations affect Speed Index?

Yes, continuous animations can negatively impact Speed Index by preventing visual stability. Consider:

  • Pausing animations during initial load
  • Using CSS prefers-reduced-motion
  • Avoiding auto-playing videos above the fold

How does lazy loading affect Speed Index?

Lazy loading below-the-fold content improves Speed Index by:

  • Reducing initial network congestion
  • Allowing above-fold content to load faster
  • Preventing resource competition

Never lazy-load above-the-fold content as it worsens Speed Index.

What's a realistic Speed Index target?

Targets vary by site type:

  • Content sites: < 3000ms
  • E-commerce: < 4000ms
  • Web apps: < 4500ms
  • Mobile: Add 1000ms to desktop targets

Focus on progressive improvement rather than absolute numbers.

Put GEO into practice

Generate AI-optimized content that gets cited.

Try Rankwise Free
Newsletter

Stay ahead of AI search

Weekly insights on GEO and content optimization.