- Project
RGW Heating & Plumbing
- Website address
- rgwplumbing.co.uk
- Role
- Designer and developer, solo
- Stack
- Next.js, React, TypeScript, Mantine, Sanity, Quote Direct API, Resend, GA4, Vercel
- Year
- 2019–2026
- What I owned
- Design and build, end to end. The funnel, the content model, measurement, and the first-party integration with Quote Direct.
- What I shipped
- A live conversion site at rgwplumbing.co.uk. An instant boiler quote on the page, ten town pages, help articles, Google reviews, and GA4 events on every step of the funnel.

Overview
A business-growth site for a two-person plumbing and heating firm in South Hampshire, not a redesign of a brochure. Homeowners searching for a boiler land on a town or service page and can get a fitted price in about 90 seconds, from the engineer who will actually fit it.
The homepage above exists to get a visitor to one of two buttons. The price and the phone number sit at the same weight, because for this firm the call is worth as much as the quote.
The problem
Paid traffic had nowhere honest to convert. Quoting still meant a phone call after hours. The site needed to bring in work, on their own domain, without turning the human call into a booking calendar.
No baseline was measured on the old site. The first 28 days after cutover, to 8 September 2026, gave 25 clicks from 10,400 impressions — a desktop click-through rate of 0.14%. The site ranked first for "boiler installation near me" and took no clicks from it. A lead audit the same month found no genuine enquiries in all 26 rows the funnel had ever recorded. That is the number the rebuild starts from, not one it has beaten.
The user journey
- Search "new boiler Eastleigh", land on a town page.
- Tap Get my boiler price.
- Give a postcode. Out of area ends there, rather than wasting anyone's afternoon.
- Answer twelve questions: fuel, current boiler, condition, whether it moves, property, floor, bedrooms, baths, showers, and one optional extra.
- Leave an email or a phone number, pick how to be contacted, and get the price. The call is the conversion.
Design decisions
The call is the conversion, not a booking.
For a trusted local firm the survey call is the work. A booking calendar would put a form between the homeowner and the person who does the job. So the funnel ends on a phone number and a callback request, and that is what GA4 counts. It is enforced in platform config rather than convention: Quote Direct carries a bookingEnabled flag, and for RGW it is off.
The price sits behind a contact detail. The funnel asks for an email or a phone number before it shows the number. That costs completions — some people will not hand over either, and leave. It buys the only thing that actually helps a two-person firm: a way to follow up on a quote that was never going to close itself on a Sunday night. The screen says plainly what the details are for, that either one alone is enough, and that they are not used for marketing. If the funnel converts worse than a plain contact form, this is the decision to look at first.
First-party fetch, not an iframe. The quote tool is not embedded. The site calls Quote Direct's funnel API from its own origin, so Google Ads attribution and Quality Score never cross a frame boundary. An iframe path existed and was wired up; it was removed rather than left dormant, because a dormant second path is a thing that gets switched on by accident.
Ten town pages, not forty-three. The site had 43 near-identical location pages generated from one template with the town name substituted in. They were consolidated into ten, behind 308 permanent redirects. Every paragraph on the survivors is genuinely local — the housing stock, the flue routing problems that come with terraces built for the railway works, FAQs that differ by town. A page that says nothing a homeowner could not guess earns nothing.
Reviews sit next to the price.
Reviews come from the Google Places API, cached for a day, rendered with the aggregate rating in Plumber schema. They sit by the quote rather than on a page of their own because they move two things at once: map-pack ranking, and whether someone believes the number they have just been shown.
How it is built
Next.js 16 on the App Router, React 19, TypeScript, Mantine 8, hosted on Vercel. Help articles and blog content come from Sanity and static TypeScript; towns, services and contact details come from typed modules so nothing is written down twice. Leads leave through Resend. GA4 runs behind a consent banner using Consent Mode v2, so no tag fires before someone says yes.
The tenancy seam. RGW is tenant one, and everything the quote engine knows about this business is injected in one file. The engine itself imports nothing from the app.
export const rgwQuoteTenant: QuoteTenantConfig = {
businessName: business.name,
phoneDisplay: business.phoneDisplay,
phoneE164: business.phoneE164,
primaryColor: 'rgwBlue',
accentColor: 'rgwOrange',
leadEndpoint: '/api/quote',
servedPostcodeAreas: allPostcodes,
pricing: createRemotePricing('/api/quote/options'),
};
Keeping supplier credentials off the client. Pricing is an interface, not a call site. The browser gets a provider that delegates to a server route, so the Wolseley key never enters the bundle — and a failure returns an empty list rather than an error state, which is what makes the fallback below work.
export function createRemotePricing(endpoint: string): PricingProvider {
return {
async getQuotes(answers: QuoteAnswers): Promise<QuoteOption[]> {
try {
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ answers }),
});
if (!res.ok) return [];
const data = (await res.json()) as { options?: QuoteOption[] };
return Array.isArray(data.options) ? data.options : [];
} catch {
return [];
}
},
};
}
Production concerns
- Resilience. When pricing fails the funnel does not show an error, it falls through to a phone number. When the mail key is missing the lead route returns a 503 that names the phone number rather than swallowing the enquiry. A daily cron checks the site is up.
- Performance. Mobile LCP was the one Core Web Vitals gap left open, and closing it was its own ticket. Rather than quote a number that goes stale, here is the live PageSpeed Insights report — generated when you click it.
- Security. The supplier credentials never reach the browser. Pricing is requested through a server route, so the key stays server-side and the client only ever sees prices — the reason the
PricingProviderindirection above exists at all. - Spam. A honeypot and a submit-timing check answer bots with a success response and send no email, so a bot learns nothing about what tripped it. Lead posts are rate-limited per IP.
- Accessibility. Not signed off, and I will not claim it is. The wizard's choice cards are unstyled buttons, so focus visibility and Enter/Space activation need a keyboard pass; that and a Lighthouse accessibility target of 95 are on the pre-launch checklist, unticked.
- Maintainability. Phone, email, hours and Gas Safe number resolve from one module. Towns live in one typed file with the redirect map beside them. Richard never edits code.
Repo. Private client work. Happy to walk through it on a call.
Measuring success
The yardstick is genuine enquiries once paid traffic is on: four a month by November 2026, from zero today. A genuine enquiry is a lead with real contact details from inside the service area, or a tracked phone tap, from someone who is not me, not Richard, and not a test.
GA4 fires quote_completed, phone_tap, whatsapp_tap, contact_form_sent and lead_email_sent, so the funnel can be read step by step rather than as one number. Four of those are verified firing on production; the contact form is not yet exercised. A public conversion rate is not honest yet, and Ads have not started — they are gated on the firm's repricing decision, which is not mine to make.
Outcomes
- A live site that can price a boiler without anyone answering the phone, and hands the caller to a human at the point where a human is worth paying for.
- Quote Direct exists because this integration needed it to. RGW is tenant one, and the quote UI built here became the design source of truth for that product's funnel — the same screens, ported rather than rebuilt. See the Quote Direct case study.
- Live merchant pricing proved on a real job in August 2026, which is what made the platform sellable to anyone else.
- 43 location pages consolidated to 10 behind 308 redirects, and a measurement baseline that exists at all — the first honest starting line this business has had.
What I would do differently
I built 43 location pages from a template and then spent a ticket deleting 33 of them. Ten good pages first would have cost less and ranked sooner.
I built the site before fixing the reviews. A review of the whole business, checked against an independent audit, found the site was never the binding constraint — thirteen Google reviews was. Reviews cost nothing, need no developer, and take about four months to build to a credible number. The engineering was the part I knew how to do, which is not the same as the part that mattered.
An icon map matched option keywords to glyphs, and "Standard boiler" quietly matched a military tank in production. It now renders nothing when a keyword is unknown, and shouts in development. Falling back to a coincidental match is worse than falling back to nothing.
More on where I draw the line between designing and building: On being a product designer who builds.