How I Improved Website Performance by 70%
DEV Community

How I Improved Website Performance by 70%

I've been working on web projects long enough to know that performance rarely gets the attention it deserves until something breaks or someone complains. In this case, nobody complained outright, but when I ran the site through Lighthouse for the first time, the score made me wince. This is the story of how I audited, diagnosed, and systematically improved a business website that was quietly bleeding visitors because of performance problems nobody had thought to look for.

The Starting Point

The project was a redesign follow-up for Ultramodern Technologies Pvt Ltd. The visual work had already been done. The site looked good. But something felt off about how it loaded, so I ran the numbers.

Initial Lighthouse scores on mobile:

  • Performance: 34
  • LCP (Largest Contentful Paint): 8.2s
  • TBT (Total Blocking Time): 620ms
  • CLS (Cumulative Layout Shift): 0.24

Desktop was better, but not by much. The mobile numbers were the ones that mattered - that's where the majority of real traffic was coming from, and those scores represented an experience that was genuinely frustrating for anyone not on a fast connection. The site was loading, technically. It just wasn't loading in a way that felt usable.

What Was Actually Wrong

Before fixing anything, I spent time understanding the problems properly. Jumping straight to solutions without a clear diagnosis usually means fixing symptoms rather than causes. Here's what the audit surfaced:

  • Images were the biggest offender. The homepage hero was a JPEG at full camera resolution - around 3.8MB - being served to users who would display it at maybe 400KB worth of actual pixels. The rest of the image assets had the same problem: uploaded at maximum resolution, never resized, never converted to a modern format. Images alone were accounting for roughly 60% of the page weight.
  • JavaScript was blocking render. There were several third-party scripts loading synchronously in the <head> - an analytics tag, a chat widget, a social media embed, and two older tracking scripts that appeared to be from campaigns that had ended. Each one was adding to the time before the browser could paint anything on screen.
  • Fonts were loaded inefficiently. Four Google Font families were being imported via CSS @import, which is one of the slower ways to load fonts. The CSS import blocks rendering until the font stylesheet loads, which then triggers additional requests for the actual font files.
  • No caching headers were set. Static assets - images, CSS, JS files - were being served without cache-control headers, meaning returning visitors were re-downloading everything on each visit rather than loading from their local cache.
  • Layout shift was happening on page load. Images without defined dimensions were causing content to jump as they loaded. The nav bar also shifted slightly once a certain script initialized, which was contributing to the CLS score.
  • The CSS bundle was oversized. The stylesheet was 280KB unminified and included styles for components that weren't being used anywhere in the current template.

What I Did to Fix It

I worked through these roughly in order of impact, which is a habit I've found useful: tackle the things that will move the numbers most first, then layer in the smaller improvements.

Image Optimization

This was the highest-leverage change. I converted all images to WebP using a build step, which on average cut file sizes by 65–75% compared to the original JPEGs and PNGs. For the hero image specifically, the file went from 3.8MB to around 180KB - same visual quality at display dimensions, a fraction of the weight.

I also added explicit width and height attributes to every <img> tag. This lets the browser allocate space for images before they load, which immediately eliminated most of the layout shift.

For responsive delivery, I implemented srcset on the larger images so browsers could request appropriately sized versions based on the device's screen size rather than loading a desktop-sized image on a phone.

Lazy Loading

I added loading="lazy" to all images that appeared below the fold. This defers their download until they're about to enter the viewport, which reduces the initial page weight significantly and lets the browser prioritize the content the user actually sees first. This is genuinely one of the lowest-effort, highest-impact changes available - one attribute per image. The improvement in initial load time was noticeable immediately.

Dealing With the JavaScript

This took more time than the image work, but it was necessary. First, I audited every third-party script and confirmed which ones were actually still in use. Two of them weren't - a tracking pixel from an old campaign and a social embed that had been removed from the page but whose script was still loading. Removing those was a straightforward win.

For the remaining scripts, I moved them from the <head> to just before </body> and added defer or async attributes where appropriate. This allows the browser to parse the HTML and begin rendering without waiting for the scripts to download and execute.

The chat widget was a harder call. It was adding around 180KB of JavaScript that loaded on every page, even pages where the widget wasn't configured to appear. I worked with the client to configure the widget to load only on the contact page and homepage, which removed it from the majority of page loads.

Font Loading

I replaced the CSS @import font loading with <link> tags in the document <head>. This hints to the browser that these resources are high-priority, starting the download earlier in the loading process.

I also added font-display: swap to the @font-face declarations so the browser uses a fallback font immediately and swaps in the custom font once it's ready, rather than showing invisible text while waiting.

I also reviewed whether all four font families were actually needed. Two of them were used in fewer than five places across the entire site. I replaced those with system font alternatives and removed the external requests entirely.

CSS Cleanup

I ran the stylesheet through PurgeCSS, which analyzes the HTML and removes CSS rules for selectors that don't exist in the markup. The 280KB stylesheet went to 47KB. I then minified it, bringing it to around 38KB. This kind of bloat is common on sites where themes or frameworks were used initially and then customized heavily - the original styles accumulate and never get cleaned up.

Caching

I added cache-control headers for static assets on the server, setting a long cache duration for files that rarely change (images, fonts, JS bundles) and shorter durations for CSS that gets updated more frequently. For a WordPress-based site, a caching plugin handles a lot of this, but on the custom setup I was working with, it required adding headers directly to the server configuration. The impact on repeat visits was significant. A returning visitor who had already loaded the site would now pull most of the page from their local cache, reducing load time to under a second.

The Results

After working through these changes over about two weeks - not because they were individually complex, but because I was testing carefully between changes to understand what each one contributed - here's where the scores landed:

  • Performance: 91 (up from 34)
  • LCP: 1.8s (down from 8.2s)
  • TBT: 90ms (down from 620ms)
  • CLS: 0.02 (down from 0.24)

The 70% improvement figure in the title is a reasonable summary of the overall performance score change. But the more meaningful numbers are the ones that reflect what users actually experience:

  • LCP under 2 seconds means most users see the main content of the page before they have a chance to get impatient.
  • TBT under 200ms means the page feels responsive immediately rather than feeling frozen after the visual content appears.
  • CLS near zero means nothing jumps or shifts while the user is trying to read.

In terms of real-world behavior, bounce rate on mobile dropped noticeably in the weeks following the changes, and average session duration increased. I don't have perfectly controlled comparison data, but the directional signal was clear.

What I Learned

A few things stood out from this project that I think apply broadly:

  • Performance audits surface different problems than visual reviews. The site looked fine. Nobody would have looked at it and said "this seems slow." But the underlying numbers told a completely different story. Running Lighthouse or PageSpeed Insights or WebPageTest on every project should probably be a default step, not something triggered by a complaint.
  • Images almost always matter most. Every site I've done a performance audit on has had image optimization as one of the top issues. It's also one of the highest-impact and lowest-risk changes available. Compressing and converting images rarely breaks anything, and the gains are immediate.
  • Third-party scripts are expensive and worth auditing periodically. They're easy to add, easy to forget about, and nobody reviews whether they're still needed. An annual audit of what's loading on every page would catch a lot of waste before it compounds.
  • The relationship between performance and SEO is real. Core Web Vitals are a ranking signal now, and I've seen enough post-optimization traffic data to believe it. But separate from rankings, a faster site keeps people on the page longer, and that behavioral signal compounds into better rankings over time regardless.
  • Smaller wins add up. The font loading change, on its own, didn't move the overall score dramatically. Neither did the CSS cleanup individually. But across eight or nine smaller improvements, the cumulative effect was substantial. Performance optimization isn't usually about one dramatic fix.

Closing Thoughts

This project reminded me that performance work is genuinely satisfying in a way that other kinds of development work sometimes isn't. The feedback loop is fast and quantifiable - you make a change, you run the test, you see the number move. There's not much ambiguity.

The other thing it reinforced is that performance is ongoing. The site I handed back after this work was significantly faster than it was before. It will also, over the next year or two, accumulate new images that weren't optimized, new scripts from new tools, new CSS from new features. The work isn't permanent - it's a baseline that needs to be maintained.

For any project I take on now, performance is part of the conversation from the beginning rather than something I revisit after the fact. The fixes are almost always simpler than they look. The cost of not making them shows up quietly, in visitors who left before the page finished loading, and in rankings that never reached where they could have been.

Comments

No comments yet. Start the discussion.