Skip to content
abdulmazid.tech
Business siteAustralia

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%.

Visit the live siteClient work — repository is private
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.

−68%
main JavaScript bundle
1,177.94 kB 371.90 kB
Recorded in the project SEO & performance audit, 22 July 2026
−99%
service worker precache payload
66,545 KiB 562 KiB
Same audit
−86%
hero image weight
748.56 kB PNG 103.17 kB WebP
Same audit
18
crawlable prerendered routes
Each with its own title, canonical, H1 and JSON-LD
0
production dependency advisories
npm audit on both client and server production trees

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

Business siteUnited Arab Emirates

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

Next.js 16 · TypeScript · MongoDB
CRM / ERPUnited Arab Emirates

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

Next.js 16 · TypeScript · PostgreSQL

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.