Most websites don't get slow overnight. They get slow the way a garage gets cluttered — one plugin, one tracking script, one uncompressed hero image at a time. The site that felt instant on launch day is sluggish eighteen months later, and nobody can point to the commit that did it.
When traffic is growing, that drift gets expensive. Slower pages mean weaker conversion on the paid traffic you're already buying, and a worse position in search results you've already earned. Below is the order of operations we use at Workaholic Developers when a client's site has outgrown its original setup — infrastructure wins first, micro-optimisations last.
Measure the field, not the lab
Google's page experience signals are three field metrics, judged at the 75th percentile of real visits:
- LCP (Largest Contentful Paint) — good at 2.5 seconds or less. How quickly your main content actually appears.
- INP (Interaction to Next Paint) — good at 200 milliseconds or less. It replaced First Input Delay in March 2024 and measures responsiveness across the whole visit, not just the first tap.
- CLS (Cumulative Layout Shift) — good at 0.1 or less. How much the page jumps around while it loads.
Lighthouse runs a simulated test on your laptop, on your office fibre. What Google reads is the Chrome User Experience Report (CrUX): 28 days of aggregated data from real Chrome users on real devices — a lot of them mid-range Android phones on inconsistent mobile data. A green Lighthouse score alongside failing CrUX data is extremely common, and CrUX is the one that counts. Put real-user monitoring in place before you change anything, so you can prove which fix moved the number.
LCP is usually an infrastructure problem
Front-end tweaks can't rescue a slow server. Work through these in order:
- Time to First Byte. If TTFB regularly exceeds 800ms, that's your whole budget gone before a single byte of HTML renders. Find out whether it's cold serverless starts, an unindexed database query, or an origin sitting a continent away from your users.
- Serve HTML from cache where you can. Getting the document itself to a CDN edge hit is the single largest TTFB win available to most sites, and it's the one teams skip because they assume every page is personalised. Usually only a fraction of pages are.
- Preload the LCP element, and never lazy-load it. A hero image with loading set to lazy is one of the most common self-inflicted LCP failures. Give it high fetch priority instead.
- Modern formats and correct sizing. AVIF with a WebP fallback, plus a real srcset, typically cuts hero image weight dramatically compared with an unoptimised JPEG.
- Enable HTTP/3 and Brotli. Both are one-click on every major CDN and both are still switched off on a surprising number of production sites.
- Cut render-blocking chains. A stylesheet that imports a font that imports another stylesheet is three serialised round trips before anything paints.
INP is a main-thread problem
INP has nothing to do with your server. It measures how long the browser's main thread is too busy to paint a response to a user's tap. The usual culprits:
- Hydration cost. Shipping a large client bundle for a page that is mostly static means the browser spends its first seconds re-animating markup it already has. Server components, islands, or simply less JavaScript all address this.
- Long tasks. Any single task over 50ms blocks input. Break heavy work into chunks and yield back to the browser between them — scheduler.yield() where supported, a setTimeout fallback otherwise.
- Third-party scripts. Chat widgets, heatmaps, tag managers and A/B testing tools are frequently the largest contributors. Load them after interaction, or on idle, and audit them quarterly — most sites are carrying at least one tag nobody owns any more.
- Event handler weight. Doing layout reads and writes inside a click handler forces synchronous reflow. Update state, let the framework batch the paint.
CLS is cheap to fix and easy to regress
Set explicit width and height (or aspect-ratio) on every image, video and iframe. Reserve space for ad slots, banners and lazy-loaded embeds before they arrive. Use font-display swap together with a metric-matched fallback so the switch to your web font doesn't reflow the paragraph. And keep cookie-consent bars fixed and overlaid rather than injected above the fold — that one pattern has ruined more CLS scores than anything else.
Build a cache hierarchy you can actually purge
Caching is only frightening when you can't invalidate it precisely. Design four deliberate layers:
- Browser — hashed static assets get a one-year max-age with the immutable directive. They never need revalidation because the filename changes when the content does.
- CDN edge — HTML gets a short s-maxage plus stale-while-revalidate, so the edge serves the slightly-old copy instantly while fetching a fresh one in the background. Visitors never wait on your origin.
- Application — Redis or an equivalent for rendered fragments, API responses and expensive aggregations.
- Database — read replicas and materialised views for the reports that hammer your primary.
Three rules that keep it safe
- Tag your cache entries. Cache tags or surrogate keys let you purge "every page featuring product 4471" instead of nuking the whole zone on each publish. Check what your CDN plan includes — tag-based purging is often a paid tier.
- Normalise the cache key. Strip marketing query parameters and irrelevant cookies at the edge, or your hit rate quietly collapses as campaigns fragment every URL.
- Mind the Vary header and bfcache. Varying on a header with many values shatters your cache, and sending Cache-Control no-store disqualifies the page from back/forward cache — turning instant back-button navigation into a full reload.
Make performance a build gate, not a quarterly panic
Every optimisation you ship decays unless something defends it. The DevOps layer is what makes performance permanent:
- Run Lighthouse CI against preview deployments and fail the pull request when LCP or total JavaScript exceeds an agreed budget.
- Set explicit bundle-size budgets per route so a new dependency shows up in review, not in next month's CrUX data.
- Alert on p75 field regressions, not averages — averages hide exactly the slow tail Google measures.
- Track cache hit ratio as a first-class metric. A falling hit rate is an early warning that someone added a cookie or a header that broke edge caching.
- Keep CDN and cache rules in infrastructure as code, so a config change is reviewable and revertible like any other commit.
Geography still decides your baseline
For clients here in Pathankot and across Punjab, the audience is overwhelmingly mobile and largely domestic — so an origin in a Mumbai region with a CDN using Indian points of presence beats an American host by a wide margin, before a line of code changes. For our Canadian clients, the split matters more: a Toronto or Montreal origin serves the home market while the CDN handles everyone else. And for teams operating across both, the answer is usually one origin close to your database, aggressive edge caching for everything read-only, and read replicas only where the write path genuinely demands it. Chasing multi-region active-active before you've cached your homepage is spending money in the wrong order.
A sane 30-day sequence
Week one: instrument RUM and read your CrUX data. Week two: fix TTFB and get HTML cacheable. Week three: images, fonts and the LCP element. Week four: cut and defer JavaScript, then add CI budgets so it stays cut. Most growing sites clear all three thresholds without a rewrite — the wins are in configuration, not reconstruction.
If your site is stuck in the amber, Workaholic Developers runs performance audits that report on field data rather than lab scores, and we implement the caching, CDN and pipeline work end to end. Tell us your slowest template and we'll tell you where the seconds are going.