Website got slower over months: how to find the real bottleneck
For non-engineers dealing with a site that has gradually become slower rather than suddenly broken. This runbook helps you check the most common causes in the right order, confirm which one is actually happening, and apply a concrete fix without guessing.
TL;DR — If your site has quietly slowed down over a few months, the most common causes are image/script bloat, disabled or misconfigured caching (storing reusable responses so pages load faster), slow database queries, and third-party scripts that now take longer to respond. Start with your browser's built-in Network tab and your provider dashboard metrics before changing server settings.\n> Reading time: ~6 min\n\n## The scenario\nIt is a normal Tuesday afternoon. Nobody has reported an outage, but your team keeps hearing the same thing in meetings: "The site just feels slower than it used to." Pages still load, checkout still works, and uptime looks fine, but product pages now take a few extra seconds and mobile feels noticeably worse. You have not made one dramatic platform change; instead, there have been months of small content edits, plugin updates, new tracking scripts, and a few feature releases.\n\n## Symptoms\n- Pages eventually load, but the first visible content appears later than before.\n- Home page or product pages feel slower even though there is no full outage.\n- Browser DevTools (built-in developer tools) shows one or more very large files, often images, JavaScript, or fonts.\n- Performance reports show rising TTFB (time to first byte — how long the server takes to start responding), often from a few hundred ms to 1s+.\n- Core Web Vitals reports show worse LCP (largest contentful paint — when the main content becomes visible), often above 2.5s.\n- Server or app logs may show slow requests such as:\n
text\nGET /products/blue-widget 200 2.843s\nGET /api/search?q=widget 200 1.921s\n\n- Database logs may show slow queries such as:\ntext\nduration: 1843.221 ms statement: SELECT * FROM orders WHERE customer_email = $1 ORDER BY created_at DESC;\n\n- Real users report that pages are worse on mobile or after scrolling, opening a menu, or reaching checkout.\n- Synthetic tests (scheduled speed checks) show a gradual decline over weeks rather than a single sharp drop.\n\n## Likely causes\n\n| Cause | How common | Quick check |\n|---|---|---|\n| Page weight has grown: oversized images, more JavaScript, more fonts | Very common | Browser: Right-click page → Inspect → Network → reload page |\n| Caching is missing, bypassed, or set too low | Very common | Browser: Inspect → Network → click HTML/CSS/JS file → Headers |\n| Slow database queries or missing indexes (database lookup helpers) | Common | In your app/server dashboard, open Metrics/Monitoring → Database/Slow queries |\n| Third-party scripts are blocking the page | Common | Browser: Inspect → Network → sort by Duration |\n| Server resources are saturated: CPU, memory, disk, or autoscaling lag | Common | In your hosting provider dashboard, open Metrics → CPU/Memory/Response time |\n| Redirect chains or DNS/CDN path changes added extra hops | Less common | Browser: Inspect → Network → click main document → check Redirects/Timing |\n\n## Step-by-step diagnosis\n1. Check whether the page got heavier. Open the slow page in Chrome or Edge, then go to Right-click → Inspect → Network, tick Disable cache, and reload. Look at the bottom totals and the largest files. If the page is now several MB larger than expected, or you see images larger than a few hundred KB each, or JavaScript bundles over about 300-500 KB compressed, this is your problem. Jump to Fixes → Page weight has grown.\n\n2. Check whether caching headers are missing or wrong. In the same Network tab, click your main HTML document, then one CSS or JS file. Open Headers and look forcache-control,etag,last-modified, and any CDN cache status header your provider adds. If HTML is always uncached when it should be cacheable, or static files havecache-control: no-cacheor very short lifetimes likemax-age=0, this is your problem. Jump to Fixes → Caching is missing, bypassed, or set too low.\n\n3. Check whether third-party scripts are slowing the page. In Network, sort by Duration or filter by domain. If long requests come from analytics, chat widgets, tag managers, A/B testing, maps, social embeds, or video players on other domains, and they start early in the page load, this is your problem. Jump to Fixes → Third-party scripts are blocking the page.\n\n4. Check server response time trends. In your hosting provider's dashboard, open the service for the site, then find Metrics or Monitoring. Look for Response time, CPU, Memory, and Request rate over the last 30-90 days. If TTFB or overall response time rose gradually with CPU pinned high, memory pressure, or request spikes, this is your problem. Jump to Fixes → Server resources are saturated.\n\n5. Check for slow database queries. In your application dashboard, APM (application performance monitoring), or database dashboard, open Slow queries or Query insights. If one or two queries dominate request time, especially on search, category, or account pages, this is your problem. Jump to Fixes → Slow database queries or missing indexes.\n\n6. Check for redirects or path changes. In Network, click the main page request and inspect Timing and any redirect chain. Ifhttpredirects tohttps, then towww, then to a language or trailing-slash version, or traffic now passes through an extra CDN/proxy hop, this is your problem. Jump to Fixes → Redirect chains or DNS/CDN path changes.\n\n## Fixes\n\n### Page weight has grown: oversized images, more JavaScript, more fonts\nCompress and resize images before upload, and serve modern formats where possible. If your CMS or build pipeline supports it, convert large JPEG/PNG files to WebP or AVIF and cap hero images to a sensible rendered size. For static sites or build pipelines, use a command like:\nbash\nnpx @squoosh/cli --webp auto --avif auto assets/images/*.{jpg,png}\n\nIf you use Nginx for static assets, enable long-lived caching for versioned files:\nnginx\nlocation ~* \.(css|js|png|jpg|jpeg|gif|svg|webp|avif|woff2)$ {\n expires 30d;\n add_header Cache-Control "public, max-age=2592000, immutable";\n}\n\nReduce unused JavaScript by removing old plugins, widgets, and page builders you no longer use. If your agency manages the build, ask them to compare bundle sizes between the current release and a release from 2-3 months ago.\n\nVerify it worked: reload with Inspect → Network and confirm total transferred size and largest file durations dropped.\n\n### Caching is missing, bypassed, or set too low\nFor static files, set a long cache lifetime. For HTML pages that can be cached safely, enable CDN or reverse-proxy caching for anonymous visitors. In your provider's dashboard, look for Caching or CDN settings; if your UI differs, the usual place is your site's service settings or edge/network settings. For Nginx static assets, use:\nnginx\nlocation ~* \.(css|js|png|jpg|jpeg|gif|svg|webp|avif|woff2)$ {\n expires 30d;\n add_header Cache-Control "public, max-age=2592000, immutable";\n}\n\nFor app responses that should be cached briefly at the edge:\nnginx\nlocation / {\n add_header Cache-Control "public, s-maxage=300, stale-while-revalidate=60";\n}\n\nIf you use a CMS, clear the application cache after changing settings so old headers do not linger.\n\nVerify it worked: in Inspect → Network → Headers, confirmcache-controlnow shows the intendedmax-ageors-maxage, and repeat visits are faster.\n\n### Slow database queries or missing indexes\nUse your database dashboard's Slow queries view first. Find the top query and add an index only for the columns actually used in filters, joins, or sorting. For PostgreSQL, a common fix looks like this:\nsql\nCREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_customer_email_created_at\nON orders (customer_email, created_at DESC);\n\nThen refresh table statistics so the planner (the part that chooses how to run a query) has current information:\nsql\nANALYZE orders;\n\nIf you have shell access and need to confirm, run:\nbash\npsql "$DATABASE_URL" -c "EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_email = 'user@example.com' ORDER BY created_at DESC LIMIT 20;"\n\n> ⚠️ Adding the wrong index can increase write costs and storage use. On large tables, schedule this with your agency or database admin, even when usingCONCURRENTLY.\n\nVerify it worked: the slow query dashboard shows the query duration dropping, and the affected page's server time falls.\n\n### Third-party scripts are blocking the page\nRemove any script you no longer need, especially old chat, heatmap, A/B testing, and social widgets. For scripts you must keep, load them later so they do not block rendering. Update script tags from blocking to deferred where safe:\nhtml\n<script src="/js/site.js" defer></script>\n<script src="https://example-analytics.com/tag.js" defer></script>\n\nFor embeds, replace auto-loading widgets with a click-to-load placeholder where possible. In tag managers, pause tags that are not actively used.\n\nVerify it worked: in Network, the slow third-party domain starts later or disappears, and LCP improves in your performance report.\n\n### Server resources are saturated: CPU, memory, disk, or autoscaling lag\nIn your hosting dashboard, increase instance size or instance count if CPU or memory has been consistently high for weeks. If your provider supports autoscaling, raise the minimum instance count so the site does not start cold every morning. For Nginx + PHP-FPM or similar app stacks, ask your agency to review worker counts and memory limits rather than guessing. If you do have shell access, confirm pressure with:\nbash\ntop\n\nand disk space with:\nbash\ndf -h\n\nIf disk is nearly full, clear old logs safely after taking a backup.\n\n> ⚠️ Restarting services can cause a brief outage. Do it in a low-traffic window if possible.\n\nVerify it worked: provider metrics show lower CPU/memory pressure and response time drops within the next traffic cycle.\n\n### Redirect chains or DNS/CDN path changes added extra hops\nUpdate your canonical URL rules so each page resolves in one redirect at most. In Nginx, collapse redirects into a single final destination:\nnginx\nserver {\n listen 80;\n server_name example.com www.example.com;\n return 301 https://www.example.com$request_uri;\n}\n\nReview DNS and CDN settings in your provider dashboard (for example, in your provider's dashboard, often DNS or Network/CDN). Remove old proxies, duplicate CDN layers, or legacy hostnames that now bounce traffic around.\n\nVerify it worked: in Inspect → Network, the main document shows zero or one redirect instead of a chain.\n\n## Prevention\n- Add a weekly page-weight check in CI so large assets fail before release. Example using Lighthouse CI:\njson\n{\n "ci": {\n "assert": {\n "assertions": {\n "total-byte-weight": ["warn", { "maxNumericValue": 1600000 }],\n "largest-contentful-paint": ["warn", { "maxNumericValue": 2500 }]\n }\n }\n }\n}\n\n- Track response time and database slow queries for 90 days, not just uptime. In your hosting and database dashboards, enable alerts for rising p95 latency (95th percentile — the slower end of normal requests) and top slow queries.\n- Pin cache headers for static assets in config so they do not change accidentally during deploys:\nnginx\nlocation ~* \.(css|js|png|jpg|jpeg|gif|svg|webp|avif|woff2)$ {\n expires 30d;\n add_header Cache-Control "public, max-age=2592000, immutable";\n}\n\n- Keep a third-party script inventory in your tag manager or CMS. Once a month, remove any script with no active owner or business purpose.\n- Add image size limits to your CMS workflow or content checklist: hero images under 300 KB where possible, thumbnails under 100 KB, and modern formats by default.\n- Run one scheduled synthetic speed test against your top 3 pages daily and alert on regressions larger than 20%. Even a simple external check that records TTFB and full load time is enough to catch slow drift early.\n
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI