A preload announces a resource before its normal consumer discovers it. This can help when a needed image is created by JavaScript or a font is discovered only after loading and parsing a stylesheet. The benefit is an earlier opportunity to fetch. It does not follow that every hinted asset becomes the browser’s highest priority or that every page becomes faster.
Discovery, fetching and use are separate
Modern browsers can scan ahead in markup for resources. An image appearing later in HTML is therefore not automatically discovered only after the main parser reaches it, and being below the fold does not itself prove late discovery. Inspect the actual request initiator and timing before adding a hint. A resource already discovered promptly may gain little and compete with more important work.
A link with rel=preload and as=image starts an image-oriented fetch when processed. It does not insert an img element. Similarly, preloading a stylesheet does not apply it, and preloading a script does not execute it. The consumer still has to request or use the resource through its normal mechanism. An early download without subsequent use is overhead, not completed rendering.
Reuse depends on compatible request information. The URL, destination expressed through as, and relevant CORS and credentials modes must agree with the eventual consumer. Font preloads commonly need crossorigin even when hosted on the same origin so their mode matches the font request. A media condition is useful for conditional loading, but it is not mandatory on every font preload. Responsive images require matching the resource selection, not merely guessing one large candidate.
When it breaks
Stale hints survive design changes. A hero image disappears from the page, yet its preload still transfers bytes. Another mistake is giving the consumer a different query string or request mode, preventing the expected reuse. Inspect both request counts and the URLs actually consumed; a visible image alone cannot prove that the earlier fetch was reused.
Preloading too many assets also competes for bandwidth, connections, and processing. Earlier discovery is not an instruction to suspend all other priorities. A hint for an optional interaction can consume data for users who never activate it. Keep such tradeoffs explicit and remove speculative hints when their cost exceeds the measured benefit for the page’s real audience.
A fast local demonstration cannot establish improved Largest Contentful Paint or responsiveness under realistic conditions. Measurements need comparable pages, cache states, devices, and network conditions. Resource timing can show when a request started; it cannot by itself attribute the whole user-visible improvement to one attribute.
Check it quickly
Save the following as preload-image.py and run python3 preload-image.py. This Python 3 command checks three page variants and an image through HTTP. It explicitly reports browser_preload_tested_by_cli false: parsing strings on the server is not a browser loading experiment.
from http.client import HTTPConnection
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Event, Lock, Thread
from urllib.parse import urlsplit
import json
import sys
requests = []
lock = Lock()
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
path = urlsplit(self.path).path
cache = 'no-store'
if path in ('/plain', '/hinted', '/unused'):
mode = path[1:]
asset = '/hero.svg?mode=' + mode
hint = f'<link rel="preload" as="image" href="{asset}">' if mode != 'plain' else ''
if mode == 'unused':
hint += '<link rel="preload" as="image" href="/unused.svg?mode=unused">'
script = '''document.getElementById('reveal').addEventListener('click',()=>{
if(document.getElementById('hero'))return;
const picture=new Image(480,160);picture.id='hero';picture.alt='Requested illustration';
picture.onload=()=>document.getElementById('state').textContent='Image displayed';
picture.src=ASSET;document.getElementById('slot').append(picture);
});'''.replace('ASSET', json.dumps(asset))
data = f'<!doctype html><html><head><meta name="viewport" content="width=device-width,initial-scale=1"><title>Preload {mode}</title><link rel="icon" href="data:,">{hint}<style>body{{font:18px sans-serif;margin:24px;max-width:700px}}button{{padding:12px 18px;font:inherit}}img{{max-width:100%;height:auto}}#slot{{margin-top:24px}}</style></head><body><h1>Preload: {mode}</h1><p>The image is inserted only after the button is pressed.</p><button id="reveal">Show image</button><p id="state">Waiting for input</p><div id="slot"></div><script>{script}</script></body></html>'.encode()
kind = 'text/html; charset=utf-8'
elif path in ('/hero.svg', '/unused.svg'):
with lock:
requests.append(self.path)
label = 'HERO' if path == '/hero.svg' else 'UNUSED'
data = f'<svg xmlns="http://www.w3.org/2000/svg" width="480" height="160"><rect width="480" height="160" fill="#4338ca"/><text x="24" y="92" font-family="sans-serif" font-size="36" fill="white">{label}</text></svg>'.encode()
kind, cache = 'image/svg+xml', 'public, max-age=600'
elif path == '/events':
with lock:
data = json.dumps(requests).encode()
kind = 'application/json'
else:
self.send_error(404)
return
self.send_response(200)
self.send_header('Content-Type', kind)
self.send_header('Cache-Control', cache)
self.send_header('Content-Length', str(len(data)))
self.end_headers()
self.wfile.write(data)
def log_message(self, *args):
pass
server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
thread = Thread(target=server.serve_forever)
thread.start()
try:
if '--serve' in sys.argv:
print(json.dumps({'origin': f'http://127.0.0.1:{server.server_port}'}), flush=True)
try:
Event().wait()
except KeyboardInterrupt:
pass
else:
for path in ('/plain', '/hinted', '/unused', '/hero.svg?mode=plain'):
connection = HTTPConnection('127.0.0.1', server.server_port, timeout=5)
try:
connection.request('GET', path)
response = connection.getresponse()
data = response.read()
assert response.status == 200
if path == '/plain':
assert b'rel="preload"' not in data
if path == '/hinted':
assert data.count(b'rel="preload"') == 1
if path == '/unused':
assert data.count(b'rel="preload"') == 2
finally:
connection.close()
print(json.dumps({'three_page_variants_available': True, 'browser_preload_tested_by_cli': False}))
finally:
server.shutdown()
thread.join()
server.server_close()
Run python3 preload-image.py --serve for that experiment. In a fresh browser profile, open the printed origin with /plain, /hinted, and /unused paths in turn. Each page inserts its hero image only after pressing Show image. The variants use different query URLs to avoid sharing one resource accidentally. Compare /events before and after each click, counting only requests for that mode. Keep caching enabled, and stop the server with Ctrl-C afterward.
Chrome 152.0.7977.83 made zero image requests before interaction on plain and one afterward. Hinted fetched its hero before the click and still had only one request after the image displayed. Unused fetched both hero and unused images before interaction, while the DOM ultimately consumed only hero. Each unused-mode asset was requested once. These observations establish early fetching, compatible reuse, and an unnecessary transfer in this fixture. They do not demonstrate a performance gain, font handling, or cross-browser equivalence. On a real page, retain a preload only after verifying its consumer and the user-facing outcome it is intended to improve.
Sources
- MDN: rel=preload, resource hints, request matching and responsive conditions, checked 2026-09-13.
- HTML Standard: Link type preload, preload processing and later consumption, checked 2026-09-13.