Web Performance

Page Speed: comprehensive website loading optimisation

Page Speed optimisation: comprehensive guide to website loading speed

Page speed is not a single metric — it is a system of measurements and optimisations that determine how quickly a user gets a working page. We cover every key layer: server, assets, scripts, cache — from diagnosis to concrete techniques.

Page Speed is one of the most impactful investments in both SEO and conversion at the same time. Amazon calculated that every 100 ms of additional latency costs 1% of sales. Google has used speed as a ranking factor for desktop since 2010, for mobile since 2018, and in 2021 introduced Core Web Vitals as a direct Page Experience signal.

The load waterfall: resources are requested one after another, and every delay pushes LCP further to the right.
53% of mobile users abandon a page that takes more than 3 seconds to load. On e-commerce this means lost conversions before a single product is shown.

What is Page Speed and how is it measured

Page Speed is not a single number — it is a set of metrics, each measuring a different aspect of the loading experience. Google PageSpeed Insights combines lab data (Lighthouse) and real-user field data (Chrome User Experience Report). The final score from 0 to 100 is a weighted sum of several metrics and should not be used as the sole optimisation target.

For SEO, field data (CrUX) is what matters: Google uses the 75th percentile of real Chrome users when evaluating Core Web Vitals. Lighthouse helps diagnose problems, but it is not a direct input to the ranking algorithm.

Key speed metrics

Each metric addresses a specific aspect of the user experience. Understanding the differences is the foundation of correct diagnosis.

MetricWhat it measuresGood valueAffects ranking
TTFB (Time to First Byte)Time until the first byte of HTML from the server< 800 msIndirectly (via LCP)
FCP (First Contentful Paint)First pixel of content on screen< 1.8 sLighthouse, not a CrUX factor
LCP (Largest Contentful Paint)Main content visible to the user< 2.5 sYes (Core Web Vitals)
INP (Interaction to Next Paint)Responsiveness to user interaction< 200 msYes (Core Web Vitals, replaced FID)
CLS (Cumulative Layout Shift)Layout stability — how much elements jump< 0.1Yes (Core Web Vitals)
TBT (Total Blocking Time)Total main-thread blocking time from JS< 200 msLab proxy for INP
FID (First Input Delay) was officially replaced by INP (Interaction to Next Paint) in March 2024. If your tooling still reports FID — update it or disregard that metric. INP measures the latency of all interactions, not just the first one.
53%

Leave within 3 s

Mobile users who abandon a page loading longer than 3 seconds

1–3%

Conversion per second

Each additional second of latency reduces conversion by 1–3% on average

75th

CrUX percentile

Google assesses Core Web Vitals at the 75th percentile of real users

28 days

CrUX window

Rolling period over which data is collected for site assessment

Diagnostics: where to start optimisation

Blind optimisation is wasted effort. The correct sequence: measure first, identify the bottleneck, fix it, measure again.

  1. Open PageSpeed Insights and test 5–10 key pages: homepage, categories, product cards, landing pages. Look at Field Data, not just the Lighthouse score.
  2. In Google Search Console go to Core Web Vitals — it shows which URLs have 'Poor' or 'Needs improvement' ratings for LCP, INP, CLS with real user data.
  3. Run WebPageTest for deep analysis: load waterfall, filmstrip, TTFB, CDN impact. Choose the test region closest to your main audience.
  4. Use Chrome DevTools → Performance → record a page load. The flame chart shows which script occupies the main thread and blocks rendering.
  5. Check Coverage (DevTools → Coverage): unused CSS/JS is a direct candidate for removal or deferred loading.
Use incognito mode and disable browser extensions when testing: ad blockers and extensions skew results. For reproducible results, use Lighthouse CLI or WebPageTest.

PageSpeed Insights

Google's free tool. Shows both Lab (Lighthouse) and Field (CrUX) data. Provides a list of specific recommendations with estimated time savings. Best starting point for an initial audit.

WebPageTest

Detailed waterfall, filmstrip, multi-location testing, version comparison. Shows the impact of CDN, DNS, SSL handshake. Indispensable for deep TTFB diagnosis.

Google Search Console

Real CrUX data by URL groups. Shows metric trends over time — the best tool for monitoring improvements after deploying optimisations.

Chrome DevTools

Performance Profiler and Coverage tabs — for finding the specific script or CSS blocking rendering. The flame chart reveals Long Tasks (> 50 ms) on the main thread.

Server and TTFB optimisation

TTFB (Time to First Byte) is the foundation of all speed. If the server responds slowly, every subsequent optimisation works against an artificial ceiling. Target: TTFB < 200 ms for CDN environments, < 400 ms for dynamic servers.

Hosting and infrastructure

Hosting choice
Shared hosting with resources split among thousands of sites physically limits TTFB. A VPS or dedicated server with Nginx/Caddy and proper caching delivers TTFB of 20–80 ms for static content.
CDN (Content Delivery Network)
Cloudflare, BunnyCDN, Fastly — the nearest edge node responds instead of your origin server. For static or cached content, TTFB drops to 10–50 ms. For dynamic content, it depends on edge cache configuration.
HTTP/2 and HTTP/3 (QUIC)
HTTP/2 eliminates head-of-line blocking and supports multiplexing. HTTP/3 over QUIC further accelerates connection establishment (0-RTT) and is resilient to packet loss on mobile networks.
Server geolocation
Physical distance between the user and the server adds ~1 ms per 100 km (round trip). For a primarily European audience, a server in Frankfurt or Amsterdam is significantly faster than US-East.

Compression and encoding

  • Enable Brotli (br) — saves 15–25% compared to gzip with comparable decompression speed. Supported by all modern browsers.
  • Keep gzip as a fallback for older clients. Minimum: compression level 6.
  • Configure compression for HTML, CSS, JS, JSON, XML, SVG. Images and binary files are already compressed — applying gzip to them achieves nothing.
  • Use the Vary: Accept-Encoding header so the CDN caches separate versions for different clients.

Server-side caching

Dynamic pages (PHP, Node.js, Python) hit the database on every request. A cache allows serving ready HTML without recalculation.

  • Full-page cache: Nginx FastCGI Cache, Varnish, Redis — store ready HTML and serve it without touching the application.
  • Object cache: Redis or Memcached for caching database query results, sessions, and computed data.
  • Invalidation rules: the cache must be purged when content changes (CMS webhook, TTL-based expiration).
  • Stale-while-revalidate: serve the stale cache immediately and regenerate in the background — users don't wait for regeneration.
For Next.js: ISR (Incremental Static Regeneration) is a built-in stale-while-revalidate mechanism. A page is generated once and updated in the background based on the revalidate: N seconds setting. TTFB is as fast as static files, while the data stays fresh.

Asset optimisation: images, fonts, CSS

Images

Images are typically the heaviest category of page payload. Correct optimisation delivers 30–70% traffic savings and directly impacts LCP.

TechniqueSavingsPriority
WebP instead of JPEG/PNG25–35%High
AVIF instead of JPEG/PNG40–55%High (90%+ browser support)
Lossless compression (PNG → oxipng)5–20%Medium
Correct sizing (srcset + sizes)50–80% on mobileHigh
Lazy loading for off-screen imagesSaves requests on loadHigh
fetchpriority="high" for LCP image0.5–1.5 s LCPCritical

Use the <picture> element to serve the right format: AVIF for supporting browsers, WebP as a fallback, JPEG/PNG for legacy browsers. The Next.js Image component handles this automatically.

Fonts

  • Self-host fonts: serve .woff2 from your own domain — eliminates the DNS lookup, TCP handshake, and dependency on the Google Fonts CDN.
  • Preload critical fonts: <link rel="preload" as="font" type="font/woff2" crossorigin> in <head> — the browser starts loading before CSS is parsed.
  • font-display: swap — the browser shows the system font while the custom one loads. Eliminates FOIT (invisible text).
  • Font subsetting: load only the needed glyphs (Latin + Cyrillic). Tools: glyphhanger or fonttools.
  • Use variable fonts — one file instead of multiple weights (regular, bold, italic). Saves 30–50% of total font size.

CSS

Critical CSS inline
Above-the-fold styles are embedded directly in <head> inside a <style> tag — the browser renders the first screen without waiting for an external CSS file. The rest of the CSS loads asynchronously.
Minification
Removing whitespace, comments, shortening properties. Cssnano or LightningCSS. Typical savings: 10–30% of the original size.
Unused CSS removal
PurgeCSS, UnCSS, or the framework's built-in mechanism (Tailwind purge). Especially important when using Bootstrap or Material UI — they pull in thousands of unused rules.
Avoid @import in CSS
CSS @import creates sequential requests: the browser loads the file, discovers the @import, and only then starts loading the next file. Use <link> tags for parallel loading instead.

JavaScript: render-blocking and the main thread

JavaScript is the primary adversary of Page Speed on modern sites. It blocks rendering, occupies the main thread, and delays interactivity. Goal: minimal JS in the critical path, maximum use of defer/async/dynamic import.

Loading strategies

AttributeBehaviourWhen to use
NoneBlocks HTML parsing, executes immediatelyOnly critical inline code
asyncLoads in parallel, executes when ready (may interrupt parsing)Independent scripts (analytics)
deferLoads in parallel, executes after HTML is parsedMost scripts on the page
type="module"Deferred by default + ESM supportModern ESM scripts

Bundle and code splitting

  • Code splitting: split JS into chunks — load only what is needed for the current page. In Next.js this happens automatically per page.
  • Dynamic import: import('module') for components not needed on initial load (modals, tabs, filters).
  • Tree shaking: bundle only the code that is actually used. Webpack and Rollup do this for ESM packages. Check: are there CommonJS dependencies breaking tree shaking?
  • Bundle analysis: bundlephobia.com to check npm package sizes, @next/bundle-analyzer for Next.js — shows exactly what occupies space.
  • Avoid large monolithic libraries: moment.js (67 KB) → day.js (2 KB), lodash → individual utility, jQuery → native JS.
Long Tasks — main-thread tasks longer than 50 ms — are a direct cause of poor INP. Check them in DevTools → Performance. Typical culprits: parsing large JSON, synchronous localStorage access, expensive React re-renders.

SPA and server rendering

Client-side SPAs (React without SSR, Angular, Vue with CSR) return empty HTML and generate content in the browser. This leads to high LCP and poor Time to Interactive.

SSR (Server-Side Rendering)
Full HTML is generated on the server. The browser receives ready content immediately. LCP, FCP, and Lighthouse Score all improve dramatically. Requires client-side hydration — account for TTI (Time to Interactive).
SSG (Static Site Generation)
HTML is generated at build time. Maximum TTFB and LCP speed. Suitable for content that changes infrequently: blog, documentation, landing pages.
PPR (Partial Prerendering)
New in Next.js 15: a static page shell with streamed dynamic parts. The static shell is served instantly from the CDN, while dynamic data streams in as it becomes ready.

Caching: browser and CDN

Correctly configured caching is the cheapest optimisation with the highest impact for returning visitors. Goal: all static assets cached for 1 year; HTML — minimal caching or none.

Resource typeCache-ControlInvalidation strategy
JS/CSS with hash (bundle.abc123.js)max-age=31536000, immutableHash changes when the file changes
Imagesmax-age=2592000 (30 days)URL with version or Content Hash
HTML pagesno-cache or max-age=0, must-revalidateAlways revalidated with server
Fontsmax-age=31536000, immutableHash in URL or versioning
API responsesCache-Control: private, max-age=60Depends on data change frequency

  • Content-based hashing (fingerprinting): Webpack, Vite, and Next.js automatically append a content hash to filenames. When code changes — new hash, old cache is not used.
  • ETag and Last-Modified: if the client already has the resource, the server responds with 304 Not Modified and no body. Less traffic, but there is still a network round trip.
  • Service Worker: a programmable cache in the browser. Enables complex strategies (Cache-First, Network-First, Stale-While-Revalidate) for PWAs and offline support.
  • CDN Cache-Control: use s-maxage to control CDN caching independently of the browser max-age. CDN caches longer, browser caches shorter.

Third-party scripts: the hidden main culprit

Third-party scripts (analytics, chat bots, pixels, A/B testing, widgets) are often the primary cause of poor TBT and INP. The average commercial page loads 15–30 third-party scripts.

Third-party script audit

WebPageTest → 'Third-party Summary' tab shows which script blocks the main thread for how many milliseconds. Typical offenders: Intercom, Hotjar, older versions of Google Analytics (gtag without async).

Deferred loading

Load non-critical scripts with a delay: setTimeout(loadScript, 3000) after the load event. The user gets to interact with the page before analytics loads.

DNS prefetch and preconnect

<link rel="preconnect"> for domains you will definitely contact. <link rel="dns-prefetch"> for likely ones. Savings: 100–300 ms per DNS lookup + TCP handshake for each third-party domain.

Replace or remove

The best third-party script optimisation is removal. Why a paid chat bot on every page? Why 5 social pixels? Auditing scripts actually in use often reveals 30–50% that are unnecessary.

Google Tag Manager is convenient but dangerous. A single GTM tag can pull in 20 scripts added by marketing without developer review. Regularly audit your GTM container and remove stale tags.

FAQ

Core Web Vitals (Field Data from CrUX) matter more for SEO — this is the data Google uses in the ranking algorithm. Lighthouse Score is a useful diagnostic tool but not a direct ranking input. A site with Lighthouse 60 and good CrUX will rank better than one with Lighthouse 95 and poor Field Data.
CrUX updates on a rolling 28-day window. If you improve metrics today, it takes ~4 weeks for the signal to fully update in Google. Do not expect sudden ranking jumps — it is a gradual improvement. Monitor progress in Search Console → Core Web Vitals.
Lighthouse is a lab tool: it tests with fixed settings (usually Moto G Power, 4G). Real users are on slow phones, unstable 3G, from remote regions. Check Field Data in PSI and Search Console. Also look at INP: subjective 'sluggishness' is often not about loading but about click responsiveness.
In 2024–2026 AMP lost most of its advantages: Google removed the Top Stories carousel priority for AMP pages and introduced Core Web Vitals as the universal criterion. An optimised non-AMP page with good LCP ranks on equal terms with AMP. Recommendation: do not invest in AMP — invest in CWV.
Yes. Google has used Mobile-First Indexing since 2019 — it indexes and evaluates the mobile version of a site first. CrUX for mobile users is separate from desktop, and mobile data is often worse. Optimise mobile speed first.