Lighthouse (built into Chrome DevTools) and Core Web Vitals give concrete, measurable numbers — Largest Contentful Paint, Cumulative Layout Shift, Interaction to Next Paint — instead of a vague sense that a page "feels slow." Optimizing without measuring first often means fixing something that wasn't actually the bottleneck.
<!-- Blocks rendering until this CSS loads -->
<link rel="stylesheet" href="styles.css">
<!-- Doesn't block rendering; runs after parsing -->
<script src="app.js" defer></script>
By default, the browser stops rendering the page while it fetches and parses CSS and non-deferred JavaScript in the <head>. The defer attribute (or moving scripts to the end of <body>) lets the page render while scripts load in the background instead of blocking on them.
An unoptimized, full-resolution photo displayed at thumbnail size is often the largest single asset on a page by far. Serving appropriately-sized images, using a modern format (WebP or AVIF instead of PNG/JPEG where supported), and lazy-loading images below the fold typically produces the biggest measurable improvement for the least engineering effort.
<img src="photo.webp" loading="lazy" width="800" height="600" alt="...">
Setting explicit width/height also prevents layout shift as the image loads — the browser reserves the correct space immediately instead of the page jumping once the image arrives.
// Slow: forces a layout recalculation on every iteration
elements.forEach(el => {
const height = el.offsetHeight // read
el.style.height = height + 10 + 'px' // write
})
// Fast: batch all reads, then all writes
const heights = elements.map(el => el.offsetHeight)
elements.forEach((el, i) => el.style.height = heights[i] + 10 + 'px')
Reading a layout property (like offsetHeight) right after writing one forces the browser to recalculate layout immediately instead of batching it — repeated in a loop, this can visibly stutter a page. Separating all reads from all writes lets the browser batch the layout work once.
In practice, image optimization, reducing render-blocking resources, and trimming JavaScript bundle size cover the large majority of real-world performance wins. Exotic optimizations are worth pursuing only after these fundamentals are handled — they rarely move the needle as much on their own.
Comments