10 Causes of Slow Websites (and How to Fix Them)

In this article
Slow websites lose search rankings, conversions, and trust. The gap between knowing a site is slow and knowing how to fix it is where most teams get stuck.
Core Web Vitals are the modern lens for that diagnosis. LCP (Largest Contentful Paint) tells you how fast the most important content appears. INP (Interaction to Next Paint) tells you how responsive the page feels once it loads. CLS (Cumulative Layout Shift) tells you whether the layout is stable. Each of the ten causes of slow websites below maps to one or more of these metrics, so you can match a symptom to a fix.
Find your bottleneck first#
Before changing anything, measure. The same fix won't help every site: a slow LCP caused by a server response time of two seconds is a different problem from a slow LCP caused by a 4 MB hero image.
- Run a free Core Web Vitals test to see your current LCP, INP, and CLS scores for any URL.
- Open Chrome DevTools → Lighthouse for a per-page audit that surfaces specific opportunities.
- Use Real User Monitoring if you want to understand performance for actual visitors across networks and devices, not just lab tests.
Once you know where the time is going, work down the list below.
Slow server response time (TTFB)#
Everything else on the page is waiting for the server. If Time to First Byte is over 600 ms, it is mathematically impossible to hit a fast LCP. The browser hasn't even seen the HTML yet. TTFB is the foundation everything else sits on.
How to fix it:
- Upgrade hosting: Cheap shared hosting puts you on overcrowded servers with limited PHP workers. If your TTFB is high during normal traffic, the host is the bottleneck.
- Use a CDN: A CDN caches your pages closer to your visitors. For a global audience this is the single highest-leverage change you can make.
- Optimise database queries: A single slow query (a missing index, an N+1 lookup) can add seconds to every page load. Use your framework's query log or APM to find them.
- Cut redirect chains: HTTP → HTTPS → www → trailing slash adds round trips before the real response. Configure your server to land on the canonical URL in one hop.
- Cache dynamic responses: Set
Cache-Controlheaders on static assets and use a server-side cache like Redis or Varnish for any response your CMS regenerates on every request.
- Time to First Byte: What it is and How to Make Improvements — measure and reduce server response time.
- The Ultimate Guide to Content Delivery Networks — how CDNs work and how to choose one.
Render-blocking JavaScript and CSS#
When the browser parses your HTML and encounters a <script> or <link rel="stylesheet"> in the <head>, it stops rendering until that resource is downloaded and processed. This is the single biggest cause of slow LCP for most sites.
How to fix it:
- Defer non-critical JavaScript: Add
deferto scripts that don't need to run before paint, orasyncfor independent scripts like analytics. Reserve render-blocking scripts for the small amount of code your initial render genuinely needs. - Inline critical CSS: Extract the styles needed for above-the-fold content and inline them in the
<head>. Load the rest with<link rel="stylesheet" media="print" onload="this.media='all'">or similar. - Use
fetchpriorityand<link rel="preload">: Tell the browser which resources matter most. Afetchpriorityhint on your LCP image can cut seconds off load time. - Avoid
@importin CSS: It chains requests serially. Inline or<link>instead.
Large image and video files#
Unoptimised images and videos are still the largest single byte cost on most pages. They are also the easiest to fix; there is no architectural change required, only better assets.
How to fix it:
- Remove anything unnecessary: If the image is decoration, drop it. The fastest asset is the one you don't send.
- Choose modern formats: Convert raster images from PNG and JPEG to WebP or AVIF. Vectors should be SVG. For video, use MP4 with H.264 or AV1 for new content. Never use animated GIFs; encode them as muted, looping MP4s instead.
- Serve responsive sizes: Use
srcsetandsizesso small screens don't download desktop-sized hero images. - Compress automatically: Use our GitHub Image Actions to optimise images directly in pull requests.
- Set
fetchpriority="high"on the LCP image andloading="lazy"on everything below the fold.
Third-party scripts#
Third-party scripts — analytics, chat widgets, A/B testing, ad networks, embeds — are code you load but don't control. They are often the largest contributor to both byte cost and main-thread time, and they evolve without your involvement.
How to fix it:
- Audit ruthlessly: Run Calibre's synthetic monitoring to test your pages with each third-party isolated. Many add no value worth their cost.
- Choose lighter alternatives: Swap Google Analytics for Plausible or Fathom. Replace heavy chat widgets with simpler alternatives or load them only on interaction.
- Use facades: Replace expensive embeds (YouTube, social, maps) with a static placeholder that only loads the real widget when the user clicks.
- Delay non-critical scripts: Push analytics and tracking after the page is interactive. They don't need to block the user's first action.
- Set a third-party budget: Cap how many third parties any page can ship. Without a budget, the count only ever grows.
Slow interactions and a blocked main thread#
Interaction to Next Paint joined the Core Web Vitals in March 2024 and is now the most commonly failed of the three. Even pages that load fast can feel sluggish if a click, tap, or key press takes hundreds of milliseconds to respond, usually because JavaScript is hogging the main thread.
How to fix it:
- Break up long tasks: Any task over 50 ms blocks the main thread. Split heavy work with
setTimeout,requestIdleCallback, or the newscheduler.yield()API. - Move work off the main thread: Use Web Workers for expensive computation that doesn't need DOM access.
- Trim event handlers: A click handler that runs 200 ms of work will always produce a poor INP. Profile your interactions in DevTools' Performance panel and cut what isn't needed.
- Debounce input handlers: Search-as-you-type and similar handlers fire on every keystroke. Debounce them or move the expensive part to an idle callback.
Unoptimised JavaScript#
Even after you defer it, JavaScript still has to be downloaded, parsed, compiled, and executed. Smaller bundles run faster on every device.
How to fix it:
- Minify and compress: Use a modern bundler (esbuild, Rollup, Vite) and serve your responses with Brotli compression. Brotli has been supported by every major browser since 2017 and compresses 15–25% better than Gzip.
- Code-split: Break your JavaScript into smaller bundles and load each route lazily. Users shouldn't download the checkout bundle on the homepage.
- Audit your dependencies: Use Bundlephobia to spot bloated libraries. A 50 KB date library you imported for one format string is a tax on every visitor.
- Use server-side rendering: SSR moves work off the client and reduces the JavaScript needed for the initial render. Especially valuable for content-heavy pages — see our guide on Next.js performance.
- Prefer CSS for animation: Anything you can do with CSS transitions, animations, or scroll-driven animations will be faster than the JavaScript equivalent.
Unoptimised CSS#
CSS is render-blocking by default, and oversized stylesheets delay first paint on every page they load on. They get less attention than JavaScript but matter just as much.
How to fix it:
- Avoid layout thrashing: Reading and writing layout properties in a loop forces repeated reflows. Batch reads, then writes. The team at dev.to has a great explainer.
- Remove unused CSS: Most sites ship CSS for components that are never used on a given page. Audit with PurgeCSS or your bundler's tree-shaking. Read more in our CSS performance guide.
- Minify and split: As with JavaScript, ship minified, Brotli-compressed CSS. Split large stylesheets by route so each page loads only what it needs.
- Use modern selectors and properties:
content-visibility: autolets the browser skip rendering off-screen content.containisolates layout work to a single element.
Web fonts#
Web fonts often add hundreds of kilobytes and can delay text rendering by seconds if handled poorly. The goal is not to avoid them but to load them in a way that doesn't punish users.
How to fix it:
- Self-host fonts: Avoid third-party font services where possible. Self-hosting removes a DNS lookup and a render-blocking dependency.
- Use
font-display: swap: This shows a fallback font immediately and swaps to the web font once it loads, avoiding the flash of invisible text. - Subset and preload: Strip glyphs you don't use, then
<link rel="preload">the subset that renders above the fold. - Use variable fonts: A variable font ships every weight and width in a single file, often smaller than two static fonts.
- Serve WOFF2: Roughly 30% smaller than WOFF, supported by every modern browser.
- Set
size-adjustto prevent layout shift: Match the fallback font's metrics to the web font to avoid CLS during the swap.
Plugins, themes, and page builders#
If you run on WordPress, Shopify, Webflow, or any other CMS, your performance is often dictated by the plugins, themes, and apps you install. Each one adds JavaScript, CSS, and database queries to every page — usually whether it's needed or not.
How to fix it:
- Audit what you actually use: Deactivate anything you can't immediately justify. If the site still works, leave it off.
- Test plugins individually: Some "performance" plugins make things worse. Run our Core Web Vitals test before and after toggling each one to see the real impact.
- Choose performance-focused themes and apps: A heavy theme can ship hundreds of kilobytes of CSS for components you never use. Lightweight, modern alternatives exist for every platform.
- Limit page-builder bloat: Drag-and-drop builders are productive but often emit deeply nested markup and inline styles. Trim what you can or move critical pages to handcrafted templates.
Aggressive bot and AI crawler traffic#
Bots now account for more than half of global web traffic, and AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended) are the most aggressive segment. They fetch faster than traditional search bots, ignore typical rate-limiting heuristics, and can saturate small servers without warning. The result is higher TTFB for real users and degraded Core Web Vitals in the field.
How to fix it:
- Watch for traffic spikes with no business signal: Sudden bandwidth or CPU spikes with no matching analytics traffic usually mean a crawler.
- Rate-limit aggressively: Configure your CDN or web server to throttle requests per IP and per user-agent. Cloudflare, Fastly, and most other CDNs offer bot management out of the box.
- Use
robots.txtdeliberately: Decide which AI crawlers you want to allow.User-agent: GPTBot/Disallow: /blocks ChatGPT's training crawler; equivalents exist for Anthropic, Google, and Perplexity. - Cache HTML at the edge: A crawler hitting your edge cache costs nothing; a crawler hitting your origin can cost a lot. Cache as much HTML as your CMS allows.
- Monitor with Real User Monitoring: If field LCP is worse than lab LCP, server contention from bots is a likely cause.
Make speed a team habit#
The fastest sites aren't fast by accident. They're fast because someone is paying attention. To make that someone the whole team, see our guides to Speed Teams, Performance Budgets, and Core Web Vitals.
