Cutting a roofing company’s page weight by two thirds
A Perth roofing business with a heavy single-page app that search engines could barely read. Rebuilt the delivery layer around prerendered HTML and cut the main JavaScript bundle by 68%.
- Market
- Australia
- Type
- Business site
- Delivered
- 2026
- Duration
- 8 weeks
Measured results
Each figure records where it came from. Nothing here is an estimate or a round number chosen because it reads well.
The brief
Roofing Services Perth is a trades business in Western Australia offering roof cleaning, painting, restoration, re-bedding, gutter work and leak repair. Their site was a React single-page app with a custom admin panel — good functionality, but everything about how it reached a browser was working against them.
The application itself was sound. The problem was entirely in delivery: a single JavaScript bundle over a megabyte, a service worker precaching sixty-five megabytes of assets, hero images shipped as unoptimised PNGs, and — because it was a client-rendered SPA — no meaningful HTML for a crawler to read on first response.
What was wrong
Three things were wrong, and only one of them was visible to the client.
The site was slow, and nobody could say by how much. There was no recorded baseline, so every previous "optimisation" was unverifiable.
Search engines were seeing an empty page. Every URL returned the same shell. Unknown URLs returned HTTP 200 instead of 404, so search engines were being invited to index pages that did not exist. Both www and the apex domain returned 200, splitting authority across two hostnames.
The production build was pointing at localhost. The deployed bundle sent API requests to localhost:5000 — every visitor's own machine. This is the kind of failure that looks like "the site is a bit flaky" until someone reads the network tab.
What I did
Measure before touching anything. Every figure below has a recorded before-value, so each change is verifiable rather than asserted.
Prerender the routes. A build step generates real HTML for 18 routes, each with its own title, canonical URL, H1 and JSON-LD, before React ever executes. A crawler now gets a complete document on first response.
Fix the indexation surface. Unknown URLs return a real 404. www permanently redirects to the apex domain. X-Robots-Tag: noindex, nofollow on the login and dashboard routes, so admin pages stop appearing in search.
Cut what ships. Code splitting on the main bundle, a service-worker precache list reduced to what is actually needed for offline use, and images converted to WebP at appropriate dimensions.
Close the security gaps found along the way. Blog mutation endpoints were reachable without authentication. Production dependency audits were brought to zero advisories on both client and server.
Built with
React · Vite · Node.js · Express · MongoDB · Nginx · PM2 · Cloudinary
More work
Twenty-five services, two languages, and a digital size passport
A Dubai bespoke tailor with a 25-service catalogue in English and Arabic, UAE compliance pages written against the actual legislation, and stored customer measurements.
25 services in the searchable catalogue
A storefront and an ERP in one bilingual system
A Dubai and Sharjah furnishing business running on WhatsApp threads and spreadsheets. Built one bilingual Next.js system that serves customers at the front and runs leads, quotations, orders and invoicing at the back.
1 REST endpoints in the entire application
Have something like this in mind?
A short call to work out what it would actually take. Free, and you get a written scope and price before anything is committed.