View contents
Introduction
This comprehensive guide walks you through advanced fetchpriority implementation strategies. You’ll learn to debug priority issues, combine fetchpriority with other optimization techniques, and build a systematic approach to resource prioritization.
What you’ll learn:
- Identifying LCP elements for priority optimization
- Debugging resource priority in Chrome DevTools
- Combining fetchpriority with preload and preconnect
- Framework-specific implementations
- Server-side dynamic prioritization
- Measuring and validating improvements
1. Identifying Your LCP Element
Before adding fetchpriority, you must identify exactly which element is your LCP. Different pages may have different LCP elements.
Using PageSpeed Insights
- Navigate to PageSpeed Insights
- Enter your URL and run analysis
- Scroll to “Diagnostics” section
- Find “Largest Contentful Paint element”
- Note the element selector and type
Using Lighthouse in DevTools
// In DevTools Console, after running Lighthouse
const lcpEntry = performance.getEntriesByType('largest-contentful-paint')[0];
console.log('LCP Element:', lcpEntry.element);
console.log('LCP Time:', lcpEntry.startTime);
Using Performance Observer
Add this to your page for real-time LCP monitoring:
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP Element:', lastEntry.element);
console.log('LCP Time:', lastEntry.startTime, 'ms');
console.log('LCP URL:', lastEntry.url || 'text element');
}).observe({ type: 'largest-contentful-paint', buffered: true });
Common LCP Elements by Page Type
| Page Type | Typical LCP Element | Priority Action |
|---|---|---|
| Homepage | Hero image/banner | fetchpriority="high" on hero img |
| Product page | Main product image | fetchpriority="high" on product img |
| Blog post | Featured image or first heading | fetchpriority="high" if image |
| Category page | First visible product image | fetchpriority="high" on first 1-2 |
| Landing page | Above-fold hero section | fetchpriority="high" on CTA area image |
2. Debugging Priority in DevTools
Chrome DevTools provides tools to inspect how browsers prioritize resources.
Network Panel Priority Column
- Open DevTools → Network tab
- Right-click column headers → Enable “Priority”
- Reload page
- Observe priority column values
Priority Levels Explained:
| Priority | Description |
|---|---|
| Highest | Render-blocking resources (main CSS, sync scripts) |
| High | Critical resources in viewport |
| Medium | Scripts, visible images |
| Low | Below-fold resources, async scripts |
| Lowest | Prefetch, speculative loads |
Identifying Priority Changes
Resources may change priority during load. To see changes:
- Network panel → Right-click → “Big request rows”
- Hover over resources to see initial vs. final priority
- Look for resources that started “Low” but changed to “High”
Performance Panel Waterfall
For deeper analysis:
- Performance tab → Record page load
- Expand “Network” section
- Look at request timing and ordering
- Identify bottlenecks where high-priority resources wait
Console Debugging
// Log all images and their fetch priority
document.querySelectorAll('img').forEach(img => {
console.log({
src: img.src.substring(img.src.lastIndexOf('/') + 1),
fetchPriority: img.fetchPriority || 'auto',
loading: img.loading || 'eager',
inViewport: img.getBoundingClientRect().top < window.innerHeight
});
});
3. Combining with Preload
Understanding when to use preload, fetchpriority, or both.
Preload vs Fetchpriority
| Technique | Purpose | Use Case |
|---|---|---|
<link rel="preload"> |
Discover resource earlier | Resource not in initial HTML |
fetchpriority="high" |
Increase priority once discovered | Resource in HTML but needs priority boost |
| Both together | Early discovery + high priority | Critical resource like LCP image |
Preload Without Fetchpriority
When the image is in dynamic content (loaded via JavaScript):
<!-- Preload makes browser aware of resource early -->
<link rel="preload" as="image" href="hero.webp">
<!-- Image loaded later via JavaScript -->
<script>
// Image inserted dynamically
const img = document.createElement('img');
img.src = 'hero.webp';
document.querySelector('.hero').appendChild(img);
</script>
Fetchpriority Without Preload
When the image is already in HTML but needs priority:
<!-- Image in HTML, just needs higher priority -->
<img src="hero.webp"
alt="Hero image"
fetchpriority="high"
width="1200"
height="600">
Combined Approach for Maximum Performance
For LCP images that benefit from both:
<head>
<!-- Early discovery + high priority -->
<link rel="preload"
as="image"
href="hero.webp"
fetchpriority="high">
</head>
<body>
<!-- Image still gets fetchpriority for consistency -->
<img src="hero.webp"
alt="Hero image"
fetchpriority="high"
width="1200"
height="600">
</body>
Preconnect for External Images
If your LCP image is on a CDN:
<head>
<!-- Establish connection early -->
<link rel="preconnect" href="https://cdn.example.com">
<!-- Then preload with high priority -->
<link rel="preload"
as="image"
href="https://cdn.example.com/hero.webp"
fetchpriority="high">
</head>
4. Framework Implementations
React
// React component with fetchpriority
function HeroImage({ src, alt }) {
return (
<img
src={src}
alt={alt}
fetchpriority="high"
width={1200}
height={600}
/>
);
}
// With Next.js Image component (v13+)
import Image from 'next/image';
function Hero() {
return (
<Image
src="/hero.webp"
alt="Hero"
priority // This sets fetchpriority="high"
width={1200}
height={600}
/>
);
}
Vue 3
<script setup>
// In Vue 3 with Vite
defineProps({
src: String,
alt: String,
priority: {
type: Boolean,
default: false
}
});
</script>
<template>
<img
:src="src"
:alt="alt"
:fetchpriority="priority ? 'high' : 'auto'"
width="1200"
height="600"
>
</template>
Nuxt 3
<template>
<NuxtImg
src="/hero.webp"
alt="Hero image"
:preload="true"
fetchpriority="high"
width="1200"
height="600"
/>
</template>
Angular
// Component
@Component({
selector: 'app-hero-image',
template: `
<img [src]="src"
[alt]="alt"
[attr.fetchpriority]="priority ? 'high' : null"
[width]="1200"
[height]="600">
`
})
export class HeroImageComponent {
@Input() src: string;
@Input() alt: string;
@Input() priority = false;
}
Astro
---
// In .astro component
const { src, alt, priority = false } = Astro.props;
---
<img
src={src}
alt={alt}
fetchpriority={priority ? 'high' : 'auto'}
width="1200"
height="600"
/>
5. Dynamic Priority Assignment
Sometimes LCP elements vary by device or content.
Responsive LCP Images
Different images may be LCP on different viewports:
<picture>
<!-- Mobile LCP: smaller hero -->
<source
media="(max-width: 768px)"
srcset="hero-mobile.webp"
fetchpriority="high">
<!-- Desktop LCP: full hero -->
<source
srcset="hero-desktop.webp"
fetchpriority="high">
<img
src="hero-desktop.webp"
alt="Hero"
fetchpriority="high"
width="1200"
height="600">
</picture>
JavaScript-Based Priority
For dynamic content where LCP varies:
// Set priority based on viewport position
document.addEventListener('DOMContentLoaded', () => {
const images = document.querySelectorAll('img[data-auto-priority]');
images.forEach(img => {
const rect = img.getBoundingClientRect();
const inViewport = rect.top < window.innerHeight && rect.bottom > 0;
if (inViewport && rect.height > 200) {
img.fetchPriority = 'high';
}
});
});
Server-Side Priority Assignment
In Node.js/Express:
app.get('/product/:id', async (req, res) => {
const product = await getProduct(req.params.id);
// Determine if product has large hero image
const heroNeedsPriority = product.heroImage.height > 400;
res.render('product', {
product,
heroFetchPriority: heroNeedsPriority ? 'high' : 'auto'
});
});
6. Server-Side Considerations
HTTP Early Hints (103)
For even earlier resource discovery:
HTTP/1.1 103 Early Hints
Link: </hero.webp>; rel=preload; as=image; fetchpriority=high
HTTP/1.1 200 OK
Content-Type: text/html
...
Node.js Implementation
app.get('/', (req, res) => {
// Send early hints for critical resources
res.writeEarly({
link: '</hero.webp>; rel=preload; as=image; fetchpriority=high'
});
// Continue with response
res.render('home');
});
CDN-Based Priority
Some CDNs support automatic priority hints:
<!-- Cloudflare example -->
<img src="hero.webp"
data-cf-priority="high"
alt="Hero">
7. Testing Strategies
A/B Testing Priority Impact
// Simple A/B test for fetchpriority impact
const variant = Math.random() > 0.5 ? 'high' : 'auto';
// Track which variant user sees
analytics.track('fetchpriority_test', { variant });
// Apply variant
document.querySelector('.hero-img').fetchPriority = variant;
Synthetic Testing
Use WebPageTest for controlled testing:
- Run test without fetchpriority
- Run test with fetchpriority=“high” on LCP
- Compare LCP times across multiple runs
- Use filmstrip to visualize difference
Real User Monitoring (RUM)
// Track LCP with fetchpriority context
new PerformanceObserver((list) => {
const lcpEntry = list.getEntries().at(-1);
const lcpElement = lcpEntry.element;
const hasFetchPriority = lcpElement?.fetchPriority === 'high';
analytics.track('lcp', {
time: lcpEntry.startTime,
hasFetchPriority,
elementType: lcpElement?.tagName
});
}).observe({ type: 'largest-contentful-paint', buffered: true });
8. Measuring Impact
Key Metrics to Track
| Metric | What It Shows | Target Improvement |
|---|---|---|
| LCP | Time to largest element | 10-30% faster |
| TTFB to LCP | Server to visual completion | Reduced gap |
| Image load time | Individual resource timing | Earlier completion |
| Priority changes | How often resources re-prioritize | Fewer changes |
Before/After Comparison Script
// Run this in DevTools before and after adding fetchpriority
const measureLCP = () => {
return new Promise(resolve => {
new PerformanceObserver((list) => {
const entries = list.getEntries();
resolve(entries[entries.length - 1].startTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });
});
};
// Collect multiple samples
const samples = [];
for (let i = 0; i < 5; i++) {
location.reload();
samples.push(await measureLCP());
}
console.log('Average LCP:', samples.reduce((a, b) => a + b) / samples.length);
Google Search Console Monitoring
After implementing fetchpriority:
- Go to Search Console → Experience → Core Web Vitals
- Monitor LCP improvements over 28-day period
- Track “Good URLs” percentage increase
- Compare mobile vs desktop improvements
9. Common Patterns and Anti-Patterns
Patterns (Do This)
Pattern 1: Single LCP Priority
<!-- Only the LCP image gets high priority -->
<img src="hero.webp" fetchpriority="high" alt="Main hero">
<img src="feature1.webp" alt="Feature 1">
<img src="feature2.webp" alt="Feature 2">
Pattern 2: Explicit Low for Below-Fold
<img src="hero.webp" fetchpriority="high" alt="Hero">
<!-- Explicitly low for non-critical images -->
<img src="footer-logo.webp" fetchpriority="low" loading="lazy" alt="Logo">
Pattern 3: Preload + Priority for External LCP
<head>
<link rel="preconnect" href="https://cdn.example.com">
<link rel="preload" as="image" href="https://cdn.example.com/hero.webp" fetchpriority="high">
</head>
Anti-Patterns (Avoid This)
Anti-Pattern 1: Everything High Priority
<!-- DON'T: Multiple high priority dilutes effect -->
<img src="hero.webp" fetchpriority="high">
<img src="product1.webp" fetchpriority="high">
<img src="product2.webp" fetchpriority="high">
<img src="product3.webp" fetchpriority="high">
Anti-Pattern 2: High + Lazy
<!-- DON'T: Contradictory instructions -->
<img src="hero.webp" fetchpriority="high" loading="lazy">
Anti-Pattern 3: Priority on Tiny Images
<!-- DON'T: Waste priority on small resources -->
<img src="icon.svg" fetchpriority="high" width="24" height="24">
10. Troubleshooting Guide
LCP Not Improving
Possible causes:
- Wrong element identified as LCP
- Image already loading with high priority
- Network or server bottleneck is the real issue
Solution:
// Verify LCP element
new PerformanceObserver((list) => {
console.log('LCP Element:', list.getEntries().at(-1).element);
}).observe({ type: 'largest-contentful-paint', buffered: true });
Priority Not Being Applied
Check in DevTools:
- Network tab → Priority column
- Verify attribute is in HTML source
- Check for JavaScript overriding attribute
Debug code:
const img = document.querySelector('.hero-img');
console.log('fetchPriority:', img.fetchPriority);
console.log('attribute:', img.getAttribute('fetchpriority'));
Browser Not Supporting Fetchpriority
Detection code:
const supportsFeature = 'fetchPriority' in HTMLImageElement.prototype;
console.log('fetchPriority supported:', supportsFeature);
Fallback strategy: Use preload for browsers without fetchpriority support:
<link rel="preload" as="image" href="hero.webp">
Implementation Checklist
- [ ] Identify LCP element using PageSpeed Insights or DevTools
- [ ] Add
fetchpriority="high"to LCP image - [ ] Remove any
loading="lazy"from LCP image - [ ] Add
fetchpriority="low"to below-fold images - [ ] Consider preload for external or late-discovered LCP images
- [ ] Test priority in DevTools Network panel
- [ ] Run Lighthouse before and after
- [ ] Monitor Core Web Vitals in Search Console
- [ ] Set up RUM to track real-user LCP improvements
Related Articles
- Fetchpriority Explained - Introduction to the fetchpriority attribute
- LCP Optimization Guide - Complete Largest Contentful Paint optimization
- Lazy Loading Best Practices - When to use and avoid lazy loading
- Image Optimization Guide - Comprehensive image optimization strategies
References
- web.dev - Optimize resource loading with the Fetch Priority API
- web.dev - Optimize Largest Contentful Paint
- Chrome Developers - LCP request discovery
- Chrome DevTools - Analyze runtime performance
- MDN Web Docs - HTMLImageElement: fetchPriority property