Measure first
You have two kinds of data and need both. Lab data (Lighthouse, PageSpeed Insights) is repeatable under controlled conditions and great for diagnosis. Field data (the Core Web Vitals report in Search Console) shows what real users see on phones and weak networks, and it is what Google actually considers.
Watch three metrics: LCP for how fast the main content appears, INP for responsiveness and CLS for layout stability. They are explained in our Core Web Vitals guide. To start, set DevTools Network to "Slow 4G" and throttle the CPU, then load the page; the three largest requests usually give away half the problem.
Images: the biggest and easiest win
Three things are enough: serve the real display size (a 4000 px image for a 400 px box is waste), use a modern format such as WebP or AVIF, and lazy-load images below the fold. Never lazy-load the main image at the top; give it priority instead.
<!-- Hero image: high priority, explicit size to avoid layout shift -->
<img src="hero-800.webp" alt="Team at work"
srcset="hero-480.webp 480w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(max-width: 700px) 100vw, 800px"
width="800" height="450" fetchpriority="high">
<!-- Below the fold -->
<img src="card.webp" alt="Project card" width="400" height="300"
loading="lazy" decoding="async">The width and height attributes let the browser reserve space ahead of time and prevent layout shift. These features are covered in the MDN performance docs.

JavaScript: ship less, run it later
JavaScript must be downloaded, parsed and executed, and on mid-range phones that costs several times more than on a developer laptop. You have three main levers:
- Remove: use a bundle analyzer to see which libraries are heavy. Often a large date or utility library was imported for one small function.
- Split: load code that is not needed on first paint with a dynamic
import(). - Defer: load non-critical scripts with
deferorasyncso they do not block rendering.
// Load the heavy chart library only when the user opens the tab
const btn = document.querySelector('#show-chart');
btn.addEventListener('click', async () => {
const { renderChart } = await import('./chart.js');
renderChart(document.querySelector('#chart'));
}, { once: true });If you use React and Next, Server Components noticeably cut the JavaScript shipped; see Next.js 13 features.
CSS and fonts
CSS blocks rendering, so size matters. Critical above-the-fold styles can be inlined in the HTML with the rest loaded later, but for most small sites a single minified, cached CSS file is enough and simpler.
Fonts carry two risks: invisible text until the file arrives and layout shift when the fallback is swapped. The fixes are simple: load only the weights and character subsets you need, serve woff2 and set font-display. For Persian fonts, which tend to be heavier, this matters even more.
@font-face {
font-family: "Vazirmatn";
src: url("/fonts/vazirmatn-var.woff2") format("woff2");
font-weight: 100 900;
font-display: swap;
}<link rel="preload" href="/fonts/vazirmatn-var.woff2" as="font" type="font/woff2" crossorigin>Caching, compression and CDN
A returning visitor should not re-download everything. Cache files whose names change with each build (such as app.3f9a1c.js) for a long time and mark them immutable, while HTML gets a short lifetime or revalidation. Brotli or gzip compression usually shrinks text assets dramatically.
# nginx: long cache for hashed assets, compression on
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
location ~* \.(?:js|css|woff2|webp|avif)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}A CDN shortens the physical distance to the user and helps most with scattered audiences or heavy files. If your server and API caching are part of the bottleneck, see caching with Redis.
Prioritisation and final checklist
Not every task has the same effect. The table below shows our suggested order by impact versus effort; exact figures depend on your project, but the order is usually this.
| Action | Typical impact | Effort |
|---|---|---|
| Compress and resize images | High | Low |
| Remove or split JavaScript | High | Medium |
| Long caching for static files | High for repeat visits | Low |
| Font optimisation | Medium | Low |
| Taming third-party scripts | Medium to high | Medium |
Speed is also part of technical SEO; the full list of checks is in the technical SEO guide. If your site needs a review, our SEO and web development services include a speed audit.
Frequently asked questions
What is an ideal page load time?
There is no magic number, but a common target is an LCP of 2.5 seconds or less in real-user data. Steady improvement and consistency on phones and weak networks matter more than one figure.
Does site speed affect Google rankings?
Yes, page experience including Core Web Vitals is one of several signals, but it does not replace relevant content. A fast site with weak content will not rank well.
Should I lazy-load every image?
No, only images below the first screen. The main image at the top should load early and ideally get fetchpriority="high", otherwise LCP gets worse.
Which optimisation should I do first?
Usually images, since they carry the most weight and take the least effort. Then excess JavaScript and caching. First confirm with Lighthouse and the Network tab that these are your real bottlenecks.

Comments
No comments yet. Be the first to share your thoughts.