Website speed comes down to a handful of factors: how quickly the server sends the first byte of its response (TTFB), how far the server is from the visitor, how many files the browser has to download and how heavy they are (images, scripts and fonts above all), which layers have caching turned on, and how many expensive database queries each page runs. In practice, one or two of these cause most of the slowness and the rest contribute very little.
The easiest place to start testing is Google's free PageSpeed Insights, followed by the Network tab in your browser's developer tools. This article walks through the factors one by one, then covers Core Web Vitals and how to measure them, and ends with a prioritized checklist. If your site runs on WordPress, each section includes the WordPress-specific angle.
Short answer: Website speed depends mostly on server response time (TTFB), the distance between server and visitor, the weight of images, scripts and fonts, caching, and database queries. One or two of these usually account for most of the delay, so measure first with PageSpeed Insights and fix the biggest bottleneck before anything else.
Server response time (TTFB)
TTFB, or Time to First Byte, is the time between the browser's request and the arrival of the first byte of the response. Everything the server does before it sends HTML adds to this number: running PHP (or whatever language you use), database queries, calls to external services and rendering the page.
Google's guidance treats a TTFB under 0.8 seconds as good, but for an ordinary cached page, a few tens to a few hundred milliseconds is achievable. If TTFB is high, optimizing images and CSS won't help; the problem is on the server.
Common causes: an overcrowded shared host (VPS vs. shared hosting), no page cache, an old PHP version or OPcache turned off, slow queries, and calling an external API in the middle of rendering a page.
Server location relative to visitors
Opening a page takes several round trips between browser and server: DNS lookup, TCP connection, TLS handshake and then the request itself. If the server is on another continent, each of those adds tens to hundreds of milliseconds.
The rule is simple: host close to most of your visitors. If your audience is mostly in one country, a data center in or near that country usually gives the lowest latency; if it's international, pick a location close to the bulk of it. Also enable HTTP/2 or HTTP/3 on your web server so files are fetched in parallel over a single connection. Browsers only support these over HTTPS (free SSL with Let's Encrypt).
Images: the heaviest part of most pages
On most sites, images are the bulk of the page weight. Three mistakes are common: outdated formats (JPEG and PNG, when WebP or AVIF is usually far smaller at the same visual quality and every modern browser supports them), wrong dimensions (a 4000-pixel photo in an 800-pixel slot), and downloading every image up front, including those far down the page. The fix in HTML is straightforward:
<img
src="/images/product-800.webp"
srcset="/images/product-400.webp 400w, /images/product-800.webp 800w, /images/product-1600.webp 1600w"
sizes="(max-width: 600px) 100vw, 800px"
width="800" height="600"
loading="lazy"
alt="Front view of the product">
srcset and sizes let the browser pick the smallest version that's good enough, loading="lazy" defers the download until the user scrolls near it, and width/height reserve the space so the layout doesn't jump. One exception: don't lazy-load the main image at the top of the page. It's usually the LCP element, and you can even give it fetchpriority="high".
To batch-convert images on Linux, cwebp from the webp package is all you need:
sudo apt install webp
for f in *.jpg; do cwebp -q 80 "$f" -o "${f%.jpg}.webp"; done
On WordPress, image optimization plugins automate this, and WordPress itself adds srcset and lazy loading by default.
Caching at every layer
Caching means keeping the result of expensive work so you don't have to do it again. It happens at several layers:
| Layer | What it stores | Effect |
|---|---|---|
| Browser cache | Static files on the user's device | Repeat visits download almost nothing |
| CDN | Static files close to the user | Lower network latency |
| Page cache | Fully rendered HTML on the server | Very low TTFB |
| Object cache | Query and computation results (e.g. in Redis) | Less load on the database |
| OPcache | Compiled PHP code | Faster execution of every request |
For the browser cache, files whose names change on every build (like app.3f9c2a.css) can be cached for a long time. In Nginx:
location ~* \.(css|js|webp|avif|png|jpg|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
This is only safe for fingerprinted (versioned) file names; otherwise users will keep seeing the old version for up to a year.
On WordPress, a page caching plugin is probably the single biggest win for the least effort. For Laravel, what to cache, where and for how long is covered in Laravel caching: where and how long, and Redis itself is explained in What is Redis.
Too many scripts and plugins
Every JavaScript file has to be downloaded, parsed and executed, and while that happens the page may not respond to clicks. The usual suspects: several analytics tools at once, chat and map widgets on every page, heavy sliders, and on WordPress, plugins that load their scripts everywhere and multipurpose themes packed with features you never use. Plugin count on its own isn't the metric; one bad plugin is worse than ten lightweight ones. But delete unused plugins rather than just deactivating them.
For the scripts you keep, use defer so they don't block rendering:
<script src="/js/app.js" defer></script>
Fonts
A web font often ships in several weights, and each weight is a separate file. Load only the two or three weights you actually use, serve them as woff2, set font-display: swap so text doesn't wait for the font to arrive, self-host fonts on your own domain, and preload the main body font so it's requested early:
<link rel="preload" href="/fonts/body-regular.woff2" as="font" type="font/woff2" crossorigin>
Database queries
A page that runs 21 queries to show 20 products (one for the list, one per product) is the classic N+1 problem. With a few rows you won't notice; as the data grows, it drags the page down. The main remedies:
- Indexes on columns used in
WHERE,JOINandORDER BY; useEXPLAINto check whether a query actually uses one. - Eager loading in your ORM, e.g.
with()in Laravel. - The slow query log in MySQL or PostgreSQL, so slow queries reveal themselves.
- Moving heavy work to the background, such as sending email (Laravel queues).
On WordPress, a bloated set of autoloaded rows in wp_options and query-heavy plugins are common culprits, and the Query Monitor plugin shows every query on a page along with its timing.
Core Web Vitals in plain terms
Google defines three metrics to measure real user experience, and they feed into rankings:
| Metric | What it measures | Good threshold |
|---|---|---|
| LCP (Largest Contentful Paint) | When the largest piece of content (usually the hero image or main heading) becomes visible | 2.5 s or less |
| INP (Interaction to Next Paint) | How quickly the page responds after a click or keypress | 200 ms or less |
| CLS (Cumulative Layout Shift) | How much content moves around while loading | 0.1 or less |
LCP is driven mostly by TTFB, a heavy hero image and fonts; INP is usually the victim of heavy JavaScript; and CLS comes from images without dimensions, banners injected late and font swaps.
How to test website speed
PageSpeed Insights
Enter the page URL in PageSpeed Insights. The report has two parts that shouldn't be confused:
- Field data: collected from real Chrome users over the past 28 days, and only shown for sites with enough traffic. This is what Google looks at for ranking.
- Lab data: a single Lighthouse run under simulated conditions. The 0–100 score belongs to this part and fluctuates a little between runs.
A score of 100 isn't the goal; getting all three Core Web Vitals into the green is. Test the home page, a product or article page and a category page separately, and take the mobile results more seriously.
The browser's Network tab
Press F12 to open developer tools, go to the Network tab and tick Disable cache to simulate a first visit. Reload the page and look for:
- The first row (the HTML document): under Timing, "Waiting for server response" is your TTFB.
- The Size column: sort by size so large images and scripts stand out.
- The bottom of the panel: total number of requests and bytes transferred. Requests to other domains are usually third-party scripts.
- Throttling: switch to a slower profile (such as Fast 4G) to see what mobile users experience.
For a quick TTFB measurement from the command line, use curl:
curl -o /dev/null -s -w "dns: %{time_namelookup}s\nconnect: %{time_connect}s\ntls: %{time_appconnect}s\nttfb: %{time_starttransfer}s\ntotal: %{time_total}s\n" https://example.com/
Run it a few times; if ttfb minus tls is large, the time is being spent generating the page on the server.
A prioritized checklist
The order below reflects typical impact relative to effort. Start at the top and measure again after each step:
- Measure and record the numbers for a few key pages.
- TTFB: if it's above roughly 0.8 seconds, look at page caching, PHP version, OPcache and hosting first.
- Images: WebP or AVIF, correct dimensions, lazy loading below the fold,
width/heighton every image. - Unneeded scripts and plugins: remove anything you can't justify.
- Browser caching and compression: Gzip or Brotli for text files, cache headers for static assets.
- Fonts: fewer weights, woff2,
font-display: swap. - Database: slow query log, indexes, fix N+1 queries.
- Server location: compare it with where most of your users are.
- Monitoring: retest after every new plugin or major change.
Frequently asked questions
How can I test my website speed for free?
Enter your page URL in Google's PageSpeed Insights to see both real-user data and a Lighthouse lab test. For more detail, the Network tab in your browser's developer tools shows the size and download time of every file.
Does website speed affect SEO?
Yes. Google uses the three Core Web Vitals (LCP, INP and CLS) as ranking signals, based on real-user data rather than the lab score. Speed is one factor among many, though, and it doesn't replace relevant, useful content.
What is a good TTFB?
Google's guidance considers a TTFB under 0.8 seconds good, and for a cached page a few tens to a few hundred milliseconds is achievable. If TTFB is high, the problem is on the server, and optimizing images or CSS won't fix it.
Why is my WordPress site so slow?
The usual causes are no page cache, heavy multipurpose themes, plugins that load their scripts on every page, unoptimized images and too many autoloaded rows in the wp_options table. The Query Monitor plugin helps you find the slow queries on each page.
Do I need a 100 score on PageSpeed Insights?
No. The 0–100 score comes from the lab test and varies between runs. What matters is that all three Core Web Vitals are in the good range in real-user data, especially on mobile.
Wrap-up
Website speed is never down to a single factor, but one or two usually dominate: a slow or uncached server, heavy images and unnecessary scripts. Measure before changing anything, start with the biggest bottleneck and retest after each step. On WordPress, a good caching plugin, optimized images and removing unused plugins typically make the biggest difference. And the goal isn't a score of 100; it's that a real user, on a phone with an ordinary connection, sees the page quickly and can use it without waiting.