Tools & Workflow

Web performance basics: what makes a page slow and the fixes that matter most

How a browser loads a page, the Core Web Vitals and what each one measures, and the eight changes that fix most slow sites: image sizing, script loading, fonts, caching, compression, and avoiding layout shift.

By the TechTutorZone editorial team · Published · 6 min read (1,268 words)

Most slow pages are slow for the same few reasons, and none of them need a rewrite to fix. This tutorial explains what the browser does between the request and the first usable screen, what the three Core Web Vitals measure, and then goes through the changes in roughly the order of how much they usually help. The audit tool used throughout is Lighthouse, which is built into Chrome's developer tools (the Lighthouse panel) and needs nothing installed.

How a page loads

  1. The browser resolves the domain name, opens a connection (TCP, then TLS for HTTPS) and requests the HTML. Each of those steps is a round trip; on a mobile network a round trip can be 100 ms or more.
  2. It parses the HTML as it arrives. When it meets a <link rel="stylesheet"> or a <script> without defer or async, it stops rendering (and for scripts, stops parsing) until that file has downloaded and run. These are render-blocking resources.
  3. With HTML and CSS in hand it builds the layout and paints the first pixels.
  4. Images, fonts and deferred scripts arrive and the page fills in, sometimes moving things around as it does.
  5. JavaScript attaches event handlers; the page becomes interactive.

Everything on the critical path, steps 1 to 3, delays the first paint. Everything after affects how finished and how responsive the page feels.

Core Web Vitals

Google's three field metrics, measured on real users' devices, and the thresholds for "good":

MetricMeasuresGood
Largest Contentful Paint (LCP)Time until the largest visible element (usually the hero image or heading) is rendered2.5 s or less
Interaction to Next Paint (INP)How long the page takes to visually respond to a tap, click or key press, across the whole visit200 ms or less
Cumulative Layout Shift (CLS)How much visible content moves unexpectedly after it first appears0.1 or less

Lighthouse estimates LCP and CLS from a simulated slow device; INP needs real interactions, so Lighthouse reports Total Blocking Time as a proxy. The Performance panel's recording shows the actual timeline.

1. Size images correctly

Images are usually most of a page's bytes. Three fixes in order of impact: serve them at the size they are displayed (a 4000px photo in a 400px slot is 100 times too many pixels), compress them (WebP or AVIF at quality 75 to 85 is a fraction of a JPEG's size), and let the browser pick a size with srcset:

<img src="hero-800.webp"
     srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1600.webp 1600w"
     sizes="(min-width: 60em) 50vw, 100vw"
     width="800" height="500"
     alt="A workbench with a half-built bicycle wheel">

The width and height attributes do not fix the size (CSS can still make it responsive); they tell the browser the aspect ratio before the image downloads, so space is reserved and the page does not jump when it arrives. That is the single biggest CLS fix. Add loading="lazy" to images below the fold, and never to the LCP image; give that one fetchpriority="high" instead.

2. Stop scripts from blocking

<script src="/js/app.js" defer></script>       <!-- download in parallel, run after parsing, in order -->
<script src="/js/widget.js" async></script>    <!-- download in parallel, run as soon as ready -->
<script type="module" src="/js/app.js"></script> <!-- modules are deferred by default -->

A plain <script src> in the <head> stops the parser. defer is almost always what you want for your own code. async suits independent third-party snippets. Third-party scripts in general (analytics, chat widgets, ad tags, social embeds) are the most common cause of poor INP: audit them with the Network panel's "3rd-party" filter, and remove any that do not earn their cost.

3. Keep CSS small and inline the critical part

The stylesheet blocks rendering, so its size is paid before anything appears. Keep it to what the site uses (a CSS framework's unused 90% still has to download and parse), and for the fastest first paint, inline the rules for the above-the-fold layout in a <style> block and load the rest with a normal link. For a small site, one well-written stylesheet under 20 KB is fine without any inlining.

4. Fonts: system first, then self-host

A web font is a render-blocking-adjacent resource: text in that font is invisible or in a fallback until it loads. The system font stack (font-family: system-ui, sans-serif) costs nothing and looks native. If you need a specific font, self-host the WOFF2 file (so there is no extra domain to connect to), subset it to the characters you use, preload it, and use font-display: swap so text shows immediately in the fallback:

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-latin.woff2") format("woff2");
  font-display: swap;
  unicode-range: U+0000-00FF, U+2000-206F;
}

5. Compress and cache on the server

Text responses (HTML, CSS, JS, JSON, SVG) should be compressed with Brotli or gzip; it is one line in most server configurations and typically cuts them by 70%. Static files should carry a long Cache-Control: max-age with a hash or version in the filename (app.3f9a1c.js) so a change produces a new URL, while HTML is served with no-cache so it is revalidated. Check both in the Network panel's Headers tab: a content-encoding: br header and a sensible cache-control.

6. Avoid layout shift

Beyond image dimensions: reserve space for ads and embeds with a fixed-size container; never insert content above existing content after load (a late-arriving banner pushes everything down); use transform for animations rather than changing top or height; and give web fonts a fallback with similar metrics (the size-adjust descriptor in @font-face) so the swap does not reflow text.

7. Reduce JavaScript work

INP is about main-thread time. Long tasks (over 50 ms) in the Performance panel are the targets. Common causes: doing heavy work in a click handler (move it after the visual response with requestAnimationFrame or split it with setTimeout), re-rendering a whole list for one change, and layout thrashing: reading a layout property such as offsetHeight after writing a style in a loop forces the browser to recalculate layout every iteration. Read everything, then write everything.

8. Fewer, smaller requests

With HTTP/2 and HTTP/3, many small files are no longer a disaster, but each extra domain still costs a connection. Avoid CDN-hosted libraries for one function, avoid icon fonts (inline the four SVGs you use), and <link rel="preconnect"> to any third-party origin you genuinely need.

Measuring

  1. Open Lighthouse in developer tools, choose Mobile, run it in an incognito window so extensions do not interfere. Read the Opportunities section; it is sorted by estimated savings.
  2. In the Network panel, enable throttling ("Slow 4G") and reload. Watch the waterfall: anything on the left of the first paint line is on the critical path.
  3. For field data on a public site, PageSpeed Insights shows real-user Core Web Vitals from Chrome users, where enough traffic exists.

Pitfalls

  • Optimising on a fast laptop on fast wifi. Throttle. Your users are on a phone on a train.
  • Lazy-loading the hero image. It delays the LCP by exactly the amount you hoped to save elsewhere.
  • A performance budget nobody checks. Put Lighthouse in CI or at least re-run it before every release; third-party scripts creep in.
  • Trusting a single score. Lighthouse numbers vary run to run by a few points; look at the metric values and the waterfall, not just the score.

Exercise

Run Lighthouse on a site you have built (or any page) with Mobile settings. Write down LCP, CLS and the top three opportunities. Fix the first opportunity only, re-run, and record the change. Then open the Network panel with Slow 4G throttling and identify every request that finishes before the first paint; for each, decide whether it needs to be there.

Summary

  • LCP: make the biggest thing arrive fast (sized, compressed image with high priority; small blocking CSS; no blocking scripts).
  • CLS: reserve space for everything that loads late, starting with image dimensions.
  • INP: less JavaScript, shorter tasks, fewer third-party scripts.
  • Compress, cache, self-host fonts, and measure on a throttled mobile profile.