A custom-coded website has no page-builder bloat to blame. Every kilobyte on the page is one a developer deliberately put there. So when this 77-page business site loaded on mobile at a Lighthouse Performance score of just 16, the cause was not a heavy theme. It was a stack of well-intentioned decisions that each added render-blocking work to every single page.
The brief was strict: reach 90+ on both mobile and desktop across all 77 pages, without changing a single pixel of the design, copy, or color. Here is exactly how we rebuilt the site's performance from the ground up: the techniques that worked, the experiments that backfired, the risks we managed, and the Lighthouse scores before and after.
Results at a Glance
Homepage, measured in Google Lighthouse (Moto G / Slow 4G emulation). Every template across the 77-page site moved into the 90 to 100 band.
Project Snapshot
| Site type | Custom-coded (hand-written HTML/CSS/JS, no CMS) |
| Scale | 77 pages (services, case studies, packages, core pages) |
| Hosting / delivery | Cloudflare edge with Brotli compression |
| Hard constraint | Zero design, color, or layout changes |
| Tooling | Google Lighthouse, PageSpeed Insights, Tailwind CLI, WebP + pngquant |
| Approach | Fix the homepage first, then templatize the wins across all pages |
The Challenge: A Fast Stack Doing Slow Work
A Lighthouse audit of the homepage showed the problem was not one heavy asset. It was compounding overhead, multiplied across every page of the site:
| What we found | Why it hurt |
|---|---|
| In-browser Tailwind "Play CDN" compiler | Shipped a full CSS engine to every visitor and recompiled styles in the browser on load, dominating Total Blocking Time |
| Two CSS frameworks loaded | A second full Tailwind build (bundled inside Flowbite) fought the first and re-resolved the H1 size, causing layout shift |
| Oversized images | Source files up to 5,616 px wide and 12.7 MB, served at display sizes a fraction of that |
| Render-blocking hero image | A near-invisible decorative background image was the Largest Contentful Paint element |
| Unreserved dynamic content | A runtime-injected nav, an embedded form, and a slide-in popup each shifted layout as they appeared |
Individually minor. Together they pushed mobile Total Blocking Time past 1,000 ms and Cumulative Layout Shift to 0.55, exactly the kind of experience Google's Core Web Vitals penalize.
Our technical SEO service rebuilds page speed without touching your design.
The Optimization Playbook
1. Kill the in-browser CSS compiler
The site loaded Tailwind's "Play CDN", a script that compiles CSS in the browser on every page load. Convenient in development, ruinous in production. We replaced it with a single pre-built, purged stylesheet generated once by the Tailwind CLI, scanned across all 77 pages so no class is missed, then served as one cached file. This one change removed the largest block of main-thread work and cut Total Blocking Time from roughly 1,010 ms to near zero.
2. Remove the duplicate CSS framework
The pages also pulled in Flowbite's stylesheet, which bundles its own complete Tailwind build. Two frameworks meant duplicated CSS resets that re-resolved the H1's size after first paint, a measurable layout shift. The site used no Flowbite components, so we removed the CSS entirely and desktop CLS dropped from 0.65 to 0.04.
3. Right-size and re-encode every image
Across the library, 46 referenced images were oversized, several 4,800 to 5,616 px wide and up to 12.7 MB. We capped each at a sensible display width (1,600 px) and re-encoded by type: photographic JPEGs recompressed, PNGs run through pngquant, and heavy assets converted to WebP. Filenames were preserved so no markup broke.
| Asset | Before | After |
|---|---|---|
| Largest hero graphic | 12.7 MB | 248 KB |
| Portrait / team photos | 2.0 MB | 380 KB |
| Shared section PNG (10 pages) | 576 KB | 80 KB |
| Total image library | 97 MB | 49 MB |
4. Fix the Largest Contentful Paint
Two LCP problems surfaced. On the homepage, the LCP element was a decorative hero background image sitting behind an opaque gradient, invisible yet the heaviest paint. We removed it with zero visual change, and LCP began tracking the hero text instead. On another template, the hero heading sat at opacity 0 until a scroll-reveal script ran, which Lighthouse models as firing very late, so LCP read about 10 seconds. We drove the same fade with a pure-CSS animation that needs no JavaScript; LCP fell from 10.3 s to 4.1 s with an identical visual.
5. Reserve space for anything injected late
The header, footer, an embedded form, and a slide-in popup were all added after first paint. We reserved their space up front: a fixed height and top-padding spacer for the runtime-injected nav, a min-height on the form container, and visibility:hidden on the off-screen popup so it no longer counted toward layout shift. Cumulative Layout Shift went from 0.55 to under 0.07.
6. Defer, compress, and cache the rest
Non-critical work was pushed off the critical path: the
bootstrap script deferred, below-the-fold carousel CSS
loaded asynchronously, and third-party tags gently delayed
until they were needed. Everything is served from
Cloudflare's edge with Brotli compression, and every
stylesheet carries a version query (?v=6) so a
rebuild cache-busts cleanly instead of serving a stale file.
What Happened During Optimization
Real optimization is not a straight line. Several intuitive "improvements" measured worse and were reverted:
| Experiment | Outcome |
|---|---|
| Self-host the JS libraries | Slower. Faster delivery moved their parse/exec into the blocking window and TBT jumped. We kept CDN delivery. |
| Defer library JS to first interaction | TBT swung wildly run-to-run as the timed fallback drifted in and out of the trace. Reverted. |
| content-visibility: auto on sections | Broke the footer contact form, leaving a blank gap. Reverted. |
Two measurement traps also shaped the work. Lighthouse's simulated "lantern" throttling was pessimistic on text-LCP pages, disagreeing with a real-network probe, so real PageSpeed field data became the arbiter. And single PageSpeed runs swung 25+ points from third-party timing noise, so we optimized against a deterministic local Lighthouse and judged by the median of several runs, never a single score.
Risk Factors and How We Overcame Them
| Risk | How we managed it |
|---|---|
| Purged CSS could drop a class and break styling | The build scans all 77 pages; every page verified in local Lighthouse and a visual render before deploy |
| CDN serves stale same-name assets | Mandatory version cache-bust on every rebuild, unified to one version site-wide |
| Version fragmentation left a page on old CSS | A stray old version broke one page's header and footer; caught it and re-unified every reference in a single pass |
| Removing the CDN flipped CSS cascade order | A gradient card lost its background when an inline rule started winning; fixed with a higher-specificity rule |
| The "no design change" constraint | Every fix verified visually; anything that altered the look (such as content-visibility) was reverted immediately |
The Results
| Metric (homepage) | Before | After |
|---|---|---|
| Performance score — mobile | 16 | 98 |
| Performance score — desktop | 46 | 100 |
| First Contentful Paint | 2.4 s | 1.0 s |
| Largest Contentful Paint | 7.6 s | 2.2 s |
| Total Blocking Time | 1,010 ms | 10 ms |
| Cumulative Layout Shift | 0.55 | 0.05 |
All 77 templates moved into the 90 to 100 band, with most service pages hitting 99 on desktop, and none of it cost a single change to the design, copy, or color the client had signed off on.
From a 16 to a 98 mobile score with zero design changes. Speed work like this is part of our technical SEO service.
Explore Our Technical SEO ServiceKey Learnings
Convenience tooling is a production tax. The in-browser CSS compiler was the single biggest cost. Moving compilation to build time recovered most of the score in one change.
Every byte on a custom site is deliberate, so every byte is fixable. There is no theme to fight, only decisions to undo.
Measure deterministically and revert honestly. More than one best practice made things worse here. Only repeatable measurement caught it.
Cache-busting is not optional behind a CDN. A single stale stylesheet silently broke one page's header and footer.
Frequently Asked Questions
Can you improve site speed without changing the design?
Yes. Every fix here, from removing the in-browser CSS compiler to re-encoding images and deferring assets, preserved the exact design, copy, and color. The only visual change was removing a background image that was invisible behind an opaque gradient.
Why was a custom-coded site so slow?
Custom sites avoid theme bloat but can accumulate render-blocking decisions: an in-browser CSS compiler, a duplicate framework, and unoptimized images. Each cost was multiplied across all 77 pages.
How do you measure page speed reliably?
PageSpeed single runs are noisy. We use a deterministic local Lighthouse behind a Brotli-compressing server, take the median of several runs, then confirm against real-world PageSpeed field data.
Is your custom-coded site leaving speed on the table?
Get a free page-speed audit. We'll show you your Lighthouse scores, the biggest wins available, and a realistic timeline.
Get Your Free Audit