Case study 08 · Consumer retail websites · Build, extend and maintain
Three consumer brands, one website engine, and a catalogue that keeps working when the phone has no signal
Because the moment that matters most is a customer standing in the shop with one bar.
Written by Hesham Ghoniem, lead engineer ·
- Sector
- Consumer retail websites · multi-brand
- Engagement
- Build, extend and maintain
- Scope
- Public storefront engine · three brands · installable app · search visibility
- Stack
- PHP
- FastRoute
- MySQL
- Progressive Web App
- Vanilla JavaScript
- 8 — Files that diverge between two live brands, of 60
- 3 — Brands running on one engine
- 22 — Settings that define an entire brand
- 671 — Files cached so the catalogue works offline
- 379 — Pages published to search engines
Overview
Three brands needed a website each. We built one engine.
A retail group with several brands needed a proper website for each one. Each brand has its own name, city, price point, slogan and personality, but they all have the same job to do: show live stock, help someone choose, and get them through the door.
Building three websites would have solved the problem on the day it was asked and created a permanent one. Three codebases means every fix done three times, forever.
We built one engine and made a brand a settings file. Twenty-two values (name, address, phone, starting price, slogan, state, social accounts, financing partner, theme) and the same engine serves an entirely different brand. Across the two live brands, 52 of 60 program files are byte-for-byte identical, 87% of the engine. The differences amount to eight files, and each one is a genuine, deliberate brand difference rather than a copy that drifted.
The challenges
Five problems, starting with the cost of the third website.
Three websites means three times the maintenance, forever
A new feature, a legal disclaimer, a security fix: each would need doing three times, tested three times, with three chances to get it wrong. The cost of the third website is not building it. It is living with it for five years.
And the cost compounds invisibly. The three copies drift, and a year later nobody can say confidently what is different between them.
That is not a hypothetical. In this same client's back-office systems, two copies of the same software drifted 160 files apart, a story told in its own case study.
Related case study: Multi-Store Retail Operations Platform
The brands genuinely are different, and cannot be flattened
Different names, addresses, phone numbers, starting prices, slogans, social accounts, colour themes, finance providers, and different jurisdictions with different legal wording.
A shared engine that forces every brand to look and read the same is worthless, because the brands exist precisely because they are not interchangeable. The engine had to absorb real difference without forking.
The most valuable moment has the worst connection
The highest-intent moment in this business is a customer standing inside the shop, or in the car park outside it, looking at their phone while deciding. Shop interiors have poor signal. That is where the sale is won or lost.
A website that stalls at exactly that moment has failed at the only moment that really counted.
Stock changes hourly and the website must never lie
Live animals are single items. When one is sold it is gone, and a website still advertising it produces an angry phone call and a lost customer.
Conventional websites hold their own copy of the catalogue and refresh it on a schedule, which means they are wrong somewhere between every refresh.
For a business selling one-of-a-kind stock, "wrong for the next fifteen minutes" is not acceptable.
Customers do not shop by product name
Nobody searching for a family pet types a breed name. They arrive with constraints: someone in the house has allergies, they live in a flat, it is their first time, they travel a lot.
The catalogue had to be browsable by those constraints, in combination, and had to do something sensible when a combination matches nothing, because an empty page ends the visit.
Installable app · 671 files on the phone
The catalogue that works with no signal
Most websites need the internet. That is such a basic assumption that it is rarely stated, and it is exactly the assumption that fails at the moment this business most needs it to hold.
So each brand's storefront is built as an installable app. A customer can add it to their phone's home screen, where it sits with an icon and a name like any other app. There is nothing to download from an app store and nothing to approve.
And then it prepares for the signal to fail. In the background, before the customer ever needs it, the site quietly copies 671 files into the phone's own storage: every category illustration, every icon, every stylesheet, every script, every font, and the key pages of the catalogue.
By the second or third page, the phone is holding the shop. The customer walks inside, loses signal entirely, and keeps browsing.
Three phone frames side by side, at full signal, one bar and no signal. The customer is browsing the same catalogue page in all three, each marked as working, and nothing about the third frame looks degraded. Under the third: 671 files already on the device.
The design decisions that made this possible are all refusals
- No heavyweight framework. The entire front end is plain JavaScript. This is unfashionable and it is correct: a framework is hundreds of kilobytes that must arrive over the exact connection we have assumed is broken, on a phone that may be five years old.
- Versioned caching. A new release replaces the stored copy cleanly instead of leaving customers on a stale version indefinitely, the failure mode that gives offline apps their bad reputation. In the reviewed code, the storefront runs version 2.7 of its cached bundle.
- An explicit cache, not an automatic one. A named list of what must survive the loss of a connection, decided deliberately rather than whatever happened to be visited.
The website asks for the connection once, early, while the customer still has one, and then it stops needing it.
What we built
Six chapters, from a brand in one settings file to a site that is never behind the shop.
Chapter one · White-label configuration
A brand became a settings file
Everything brand-specific was pushed into one file of 22 values: name, address, phone number, email, starting price, slogan, state, social accounts, financing partner link, footer text, the staff-system address, and which visual theme to load.
Change those twenty-two values, plus the database settings that sit beneath them, and the same engine serves a different brand. Adding a brand does not involve a developer copying a folder.
One settings file of 22 values with three arrows out to three different storefronts: brand one with its own name, city and colours; brand two with its own name, price and finance partner; and brand three, unbranded, with two colour themes side by side. The same engine serves all three.
Same engine.
Chapter two · Measured, not claimed
Proving it, by measuring the difference
A white-label claim is easy to make and easy to quietly break. So it is worth stating exactly what "shared" means here.
Across the two live brands, the engine holds 60 program files. Eight of them diverge. Every one of those eight is a real brand difference: a region-targeted landing page for one brand, a promotional page for the other, and small differences in contact handling and catalogue filtering.
52 of the 60 files are byte-for-byte identical: 87% across the full set, and 93% of the files that exist in both brands. A fix written once genuinely lands in both places.
Chapter three · The brand that belongs to nobody
A third brand that exists to keep us honest
The group also runs a neutral, unbranded installation of the same engine. It has no real content. It is a template, with two colour themes side by side.
It is there so that a new brand starts from a clean, brand-free base, and so that any assumption that leaks into the shared engine shows up immediately in the version that belongs to nobody. It is the difference between claiming the platform is white-label and continuously checking.
Chapter four · One source of truth
The website does not have its own copy of the stock
This is the simplest and most important architectural decision on the page, and it is the opposite of what most agencies build.
The public website and the shop's back-office system share one database. The website does not import the catalogue, synchronise it, or refresh it on a timer. It reads the same records the staff are editing, and serves photographs from the same storage the staff upload to.
When a member of staff marks an animal sold, the website is already correct. Not within fifteen minutes: already. There is no synchronisation to fall behind, no import to fail overnight, and no window during which the website is confidently advertising something that is gone.
Chapter five · No dead ends
Browsing by constraint, with no dead ends
The catalogue is browsable by the things customers actually care about, combined freely: allergy-friendly, suitable for flats, good for first-time owners, travels well, active households, size, and pedigree versus mixed.
Filters combine as "any of these" within a category and "all of these" across categories, the arrangement people intuitively expect without being taught. And when a combination matches nothing, the catalogue widens the search rather than showing an empty page. An empty result is a visit that ends.
Chapter six · Search visibility
The boring work that brings people in
Structured information for search engines describing each store as a physical business with a postal address, map coordinates and opening hours. A hand-maintained index of 379 pages. Breadcrumb data that gives each page a readable path. An explicit, named list of which search engines and social platforms are welcome to read the site, rather than a blanket rule. Region-targeted landing pages for local searches.
Technical deep-dive · the 60-file diff
Inside the eight files that differ
Most white-label claims collapse under a diff. This one was measured, and the result is unusually clean, so it is worth walking through what the eight differences actually are.
A grid of 60 small squares. 52 are one flat colour, marked identical. Eight are highlighted, each with a one-word label: promotional page, data loader, category-name list, region landing page, catalogue filtering, listings page, pagination and contact.
- 3 Files for one brand only — A promotional landing page, its data loader, and a supporting category-name list. The other brand has no equivalent.
- 1 File for the other brand only — A region-targeted landing page, built to be found by people searching for stock in that particular area.
- 4 Files that differ in content — Catalogue filtering, the all-listings page, pagination and the contact page. Each differs in how stock is presented or how enquiries are routed.
That is the complete list.
Everything else (the routing, the catalogue logic, the detail pages, the enquiry capture, the search suggestions, the offline caching, the templating, the security handling) is one shared implementation.
Why this matters commercially, in one sentence: when a change is needed, we can say with certainty whether it affects one brand or all three, because the answer is in the file list rather than in somebody's memory.
The honest limitation
It belongs here rather than in a footnote. The views, the visible page templates holding each brand's copy, imagery and layout, differ substantially between brands. That is intentional and correct: it is where a brand's personality lives, and it is exactly what should not be shared.
But it means the accurate claim is "one shared engine, brand-specific presentation", not "one identical website with a new logo".
The engine is shared. The face is not. That is the product, and it is the only version of white-label that survives contact with real brands.
How we work
Eight rules, and half of them are refusals.
Build the second one before finishing the first
The fastest way to discover what is genuinely brand-specific is to try it early, while it is still cheap to change. Waiting until the first brand is finished means discovering the wrong abstraction at the most expensive possible moment.
Every difference becomes a setting, never a copied file
The rule was absolute: when a brand needed something different, the answer was a new configuration value. The moment the answer becomes "copy the file and edit it", the platform has quietly become three websites again.
Keep an unbranded version permanently
A neutral installation that belongs to no brand is where leaked assumptions show up immediately. It costs very little to maintain and it is the only thing that keeps a white-label claim true over years.
Assume the connection is broken
Deciding early which pages must survive with no signal shaped the entire front end, including the decision to use no framework at all. Offline was a requirement, not a feature added later.
Refuse the fashionable choice when the user's phone disagrees
Plain JavaScript on a five-year-old handset in a building with one bar beats a modern framework that has to arrive first.
Do not copy data you can read directly
The website shares the shop's database rather than importing from it. One source of truth removes an entire category of bug, the stale copy, instead of managing it.
Treat search visibility as engineering
Structured data, sitemaps, crawler rules and landing pages were built in from the start. Bolted on afterwards, they are always a compromise with decisions already made.
Never let a customer hit a dead end
An empty results page is a lost visit. The catalogue widens the search instead.
Technologies used
Eight layers, and what each one is for.
Engine
One codebase behind all three brands. 44 active public routes each.
- PHP
- FastRoute
Front end
No framework. Fast on cheap phones and weak signal: a deliberate refusal, not a limitation.
- Vanilla JavaScript
- Bootstrap grid
Offline
Installs to the home screen, caches 671 named files, and new releases replace the cache cleanly.
- Progressive Web App
- Versioned service worker
Data and images
The website reads the same records staff edit, and serves the photographs staff upload directly, already optimised. No import, no sync, no stale copy.
- MySQL, shared with the back office
- Shared storage with the staff system
- WebP conversion
Search
Describes each shop as a physical business with address, coordinates and opening hours.
- schema.org structured data
- 379-page index
- Explicit crawler rules
Discovery
Browsing by constraint rather than by product name.
- Typeahead suggestions
- Multi-tag filtering
Enquiries
A web enquiry becomes a lead in the shop's own system immediately.
- Direct capture into the shop's customer records
Finance
Applications are handled entirely by the provider. We never touch credit data.
- A regulated third-party finance provider
Automated testing
Automated tests check each change before it reaches production.
This is the stack as it stood in the reviewed code, last modified in March 2023.
How the platform works
Seven steps, from a visitor arriving to a lead in the shop's own system.
Step 1
A visitor arrives
The engine reads that brand's settings file and serves the right name, theme, contact details, starting price and financing partner.
Step 2
The site starts caching itself
In the background, 671 files move into the phone's storage. The customer notices nothing.
Step 3
They browse by constraint
Allergy-friendly, flat-suitable, first-time owner, travels well, size, pedigree: filters combine freely. If a combination matches nothing, the catalogue widens rather than showing an empty page.
Step 4
They open a listing
The full record: identification number, date of birth, colour, every vaccination given, worming, vet checks, health certificate, guarantees and transport options. It is the same record the shop's own staff maintain, not a marketing summary of it.
Step 5
Signal drops
They keep browsing. The catalogue is already on the phone.
Step 6
They apply or enquire
A finance application is handed to the brand's regulated finance provider. An enquiry lands directly in the store's back-office system as a new lead.
Step 7
Staff add a new arrival
Meanwhile, staff add a new arrival in the back office. It is on the public site immediately, because there is no copy to update.
Results
What the business can do now that it couldn't before.
Run three brands from one codebase
A fix is written once. 52 of 60 engine files are byte-for-byte identical between live brands, and the eight exceptions are documented rather than discovered.
Launch a brand as a configuration exercise
Twenty-two settings define a storefront. No fork, no copied folder, no new codebase to maintain forever.
Keep customers browsing when the signal drops
The catalogue is installable and browses with no connection: in the shop, the car park, or a basement.
Never advertise something that has sold
The website and the back office share one database. There is no synchronisation to fall behind.
Be found locally
379 pages in the published search index, structured business information with address, coordinates and opening hours, and region-targeted landing pages.
Let customers browse the way they actually think
By constraint, in combination, with no dead ends.
Turn an enquiry into a lead with no re-typing
Web enquiries land in the shop's customer records directly.
Keep credit data out of the estate entirely
Financing is handed to a regulated partner.
The engineering, in numbers
- 3 Brands running on the engine
- 22 Brand-identity settings defining a storefront
- 60 Program files in the shared engine
- 8 Files that diverge between the two live brands
- 52 of 60 Engine files byte-for-byte identical (87%)
- 44 Active public routes per storefront
- 671 Files cached for offline use
- 379 Pages in the published search index
- 2.7 Cached-bundle version in the reviewed snapshot
- 0 Front-end frameworks used
Honest status
"One shared engine, brand-specific presentation" is the honest claim. The engine (routing, catalogue logic, filtering, caching, enquiry handling) is shared, and the 8-of-60 figure is exact. The visible page templates differ substantially between brands, deliberately, because that is where brand personality belongs.
The code is a 2023 snapshot. The storefronts were last touched in March 2023, and the offline bundle version quoted (2.7) is that snapshot's. The legacy back office these sites share a database with runs on PHP 7.4 and Bootstrap 4, both long past end-of-life. Nothing on this page presents that stack as current.
Real-time updating is architectural, not a live feed. The site is always current because it reads the shop's own database directly, not because it receives push notifications. A real-time messaging service is configured in the codebase, but we found no evidence it is wired into the storefront, so we do not claim it.
Related work
Supplier Transparency Network
Retail · Until 2021 · Extend and maintain
0 — Passwords typed in front of a customer
Multi-Store Retail Operations Platform
Retail · Long-running · Build, rebuild, maintain
160 — Files that had silently drifted apart between two stores
Next step
About to build a website for your next brand?
Work with an engineering team that builds one engine instead of another codebase, measures what is really shared, and designs for the customer standing in the shop with one bar.
Client anonymised · every figure counted from a code snapshot last modified March 2023 · October 2026