Page speed
People close a slow site without waiting: on a phone with a poor connection every extra second shows. Much of the speed is decided right in HTML and CSS — by the loading order, image sizes and making sure the page does not jump while it loads.
Render-blocking CSS
Until the stylesheet from <head> is downloaded, the browser draws nothing: otherwise the user would see bare markup that jumps later. That is why CSS is called render-blocking. Hence the idea of critical CSS: the styles of the first screen — the header and the title — go right into a <style> in <head>, and the rest is loaded as a regular file. The first screen appears without waiting for anything extra:
<head>
<style>/* only the header and first-screen styles */</style>
<link rel="stylesheet" href="style.css">
</head>For a small site a simple rule is enough: one stylesheet, no unused libraries.
preload and preconnect
The browser finds some files late — for example, a font mentioned only inside the CSS. The <link rel="preload"> tag tells it to download the file at once. preconnect opens a connection to another server in advance, as in the lesson "Web fonts":
<link rel="preload" href="fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preconnect" href="https://cdn.example.com">Preload only what is surely needed on the first screen: extra preload lines take bandwidth from important files.
Images
Images are the heaviest part of most pages. loading="lazy" delays loading until the image gets close to the visible area, and fetchpriority="high" asks to download the main image before the others:
<style>
img { max-width: 100%; height: auto; display: block; margin-bottom: 8px; }
</style>
<img src="/media/photo-800x500.jpg" alt="The main photo" width="800" height="500" fetchpriority="high">
<img src="/media/photo-400x250.jpg" alt="A photo further down the page" width="400" height="250" loading="lazy">Serve a file of the right size: a 4000-pixel image in a 400-pixel box weighs dozens of times more than needed. That is what srcset from the lesson "Responsive images" is for.
No jumps
If the size of an image is unknown, the browser first gives it zero pixels, and when the file arrives it pushes the page apart. The text jumps down, and the user misses the button. This is called a layout shift. The width and height attributes or the aspect-ratio property reserve the space in advance:
<style>
.slot { aspect-ratio: 16 / 10; max-width: 320px; background: gainsboro; }
.slot img { width: 100%; height: 100%; object-fit: cover; }
</style>
<div class="slot"><img src="/media/photo-800x500.jpg" alt="Photo" loading="lazy"></div>
<p>This text will not move when the photo loads.</p>A gray rectangle of the right shape holds the place even before loading.
content-visibility and contain
content-visibility: auto lets the browser skip rendering a block while it is far off screen. On long pages this noticeably speeds up the first display. contain-intrinsic-size gives the approximate height of such a block so the scrollbar does not twitch:
<style>
section { content-visibility: auto; contain-intrinsic-size: auto 200px; padding: 8px; border-bottom: 1px solid silver; }
</style>
<section><h3>Chapter 1</h3><p>The browser renders only the visible chapters.</p></section>
<section><h3>Chapter 2</h3><p>It skips the rest until you scroll.</p></section>The contain property is a more manual option: contain: layout paint promises that the block's content does not affect the rest of the page, and the browser recalculates less.
How to measure
The Lighthouse tab of the developer tools (the lesson "Developer tools") has a Performance report. It gives a score and shows the key metrics:
| Metric | What it measures | Good |
|---|---|---|
| LCP | when the largest element of the first screen is drawn | under 2.5 s |
| CLS | how much the layout jumped while loading | under 0.1 |
| INP | how fast the page responds to clicks and taps | under 200 ms |
Test in phone mode: the processor and connection are slower there.
Common mistake
loading="lazy" on the main image of the first screen. The browser first builds the page and only then decides whether the image is visible — it starts loading later, and LCP gets worse. Lazy loading is for images below the first screen; the main one gets fetchpriority="high" without lazy.