I audited a portfolio site recently that scored 38 out of 100 on mobile. The cause was not the framework, the hosting or the amount of JavaScript. It was 9.7 megabytes of images, including a 1.5 MB animated GIF displayed at 70 pixels wide.
That is the typical shape of the problem. People reach for exotic optimisations while the actual cause sits in plain sight. Here is how to find yours.
Step 1: Measure before you change anything
Run your URL through PageSpeed Insights. Ignore the score for a moment and read three numbers:
- Largest Contentful Paint (LCP) - when the main content appears. Target under 2.5 seconds.
- Total Blocking Time (TBT) - how long the page was frozen. Target under 200 ms.
- Cumulative Layout Shift (CLS) - how much things jumped around. Target under 0.1.
Then open your browser devtools, go to the Network tab, disable cache and reload. Sort by size. The answer is usually in the top three rows.
Cause 1: Images (this is it, roughly 70 percent of the time)
Symptoms: bad LCP, huge total page weight, good TBT.
What goes wrong:
- Screenshots exported as PNG. A 1400px PNG screenshot is often 1.8 MB; the same image as WebP is 60 KB.
- Images served at full resolution regardless of display size. A 4000px photo in a 400px card wastes 95 percent of the download.
- Animated GIFs. Almost always the single largest file on a page.
- No lazy loading, so twenty below-the-fold images compete with the content you can actually see.
The fixes, in order of impact:
- Convert PNG screenshots to WebP. Typically 85 to 95 percent smaller with no visible difference.
- Resize source files to at most 2x their largest display size.
- Replace GIFs with a static image or a short video file.
- Add loading="lazy" to everything below the fold.
- Use a responsive image component so mobile gets a mobile-sized file.
Cause 2: JavaScript
Symptoms: bad TBT, bad INP, the page looks ready but does not respond to clicks.
- Whole UI libraries imported for two components
- Analytics, chat widgets and tag managers loaded before the content
- Carousels and animation libraries on pages that barely need them
- Unused dependencies still in the bundle
Check what you are actually shipping with the Coverage tab in Chrome devtools. It shows you how much of each file was used. Seeing 80 percent unused is common and clarifying.
Load third-party scripts after the page is interactive, not before. A chat widget blocking your hero render is a bad trade you did not know you made.
Cause 3: CSS and fonts
Symptoms: a delay before anything appears at all, text that flashes or shifts.
- A full CSS framework loaded when you use 15 percent of it
- The same framework loaded twice - once from npm, once from a CDN. This is more common than it sounds.
- Webfonts without font-display: swap, so text stays invisible while they download
- No preconnect to the font host, adding a full DNS and TLS round trip
Note that CSS is usually a smaller problem than people assume. A full Bootstrap build is around 26 KB gzipped. That is not what is making your site slow, and rewriting your entire markup to save it is rarely the right trade.
Cause 4: Hosting and delivery
- Shared hosting with slow server response. Anything over 600 ms time-to-first-byte is a problem.
- No CDN, so users far from your server wait for every round trip
- No compression. Gzip or Brotli should be on by default; check that it actually is.
- Redirect chains. Each hop costs a round trip.
Moving a static site to Vercel, Netlify or Cloudflare Pages fixes most of this for free and takes an afternoon.
Cause 5: Layout shift
CLS does not affect load time but it affects ranking and it makes a site feel broken.
- Images without width and height, so the page reflows when each one loads
- Ads and embeds injected without reserved space
- Fonts swapping between sizes
- Banners inserted at the top after load, pushing everything down
The fix is almost always the same: reserve the space before the content arrives.
A realistic priority order
If you only have one afternoon:
- Compress and resize every image over 200 KB
- Lazy-load everything below the fold
- Remove any duplicate CSS or JS framework loads
- Defer third-party scripts
- Add width and height to every image
- Confirm compression and a CDN are active
On the site I mentioned at the start, those steps took the page from 10.08 MB to 1.4 MB and the mobile score from 38 to 67, with no redesign and no framework change.
What not to bother with first
- Switching CSS frameworks to save 20 KB
- Micro-optimising React re-renders on a page that ships 8 MB of images
- Server-side rendering a site whose problem is asset weight
- Chasing a 100 score. 90+ is excellent; the last ten points cost more than they return.
Performance work follows the same rule as most engineering: measure, fix the biggest thing, measure again. The order matters more than the technique.

