Most web design agencies won't tell you how they build your website. I will. Every tool, every line of code, every monthly subscription. This is the full, unfiltered breakdown of how I build a professional contractor website from the ground up.
The Tech Stack
Before a single pixel hits the screen, I have to choose the right tools. This isn't WordPress. This isn't Wix. This is a custom-built, performance-optimized stack that loads faster than anything a page builder can produce.
Here's what I use:
Framework: Astro
Astro is a modern web framework that ships zero JavaScript to the browser by default. That means your website loads almost instantly because the browser isn't downloading and executing hundreds of kilobytes of JavaScript before it can show the page.
Why does this matter? Because Google measures how fast your site loads, and that measurement directly affects where you rank in search results. More on that later.
Astro uses a component-based architecture with .astro files that look like this:
---
// This runs on the server at build time, never sent to the browser
const { businessName, phone, services } = Astro.props;
---
<section class="hero">
<div class="container">
<h1>{businessName} - Licensed & Insured</h1>
<p>Serving the greater metro area for over 15 years.</p>
<a href={`tel:${phone}`} class="btn-primary">
Call {phone}
</a>
</div>
</section>
<style>
.hero {
background: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)),
url('/images/hero-bg.webp');
background-size: cover;
padding: 6rem 0;
color: white;
}
</style> The code between the --- fences runs at build time on the server. It's never shipped to the visitor's browser. The HTML below it gets pre-rendered into static HTML. The CSS is scoped to that component so it can't accidentally break other parts of the site.
If that paragraph made your eyes glaze over, that's kind of the point. This is the stuff you'd need to learn before you even start designing.
Hosting & CDN: Cloudflare Pages
Every site I build gets deployed to Cloudflare's global network. That means your website is served from data centers in over 300 cities across 100+ countries. When a homeowner in Tulsa searches "plumber near me" and clicks your site, the HTML is served from the nearest Cloudflare data center, not from some shared hosting server in Phoenix.
Cloudflare Pages connects directly to GitHub, so every time I push an update to your site's code, it automatically builds and deploys within about 45 seconds. No FTP uploads. No cPanel. No clearing caches and hoping for the best.
The Cloudflare Workers Paid plan runs $5/month per account, and I use serverless functions (called Workers) to handle things like Stripe checkout sessions and lead submissions without running a traditional server.
Database: Supabase
Every lead that comes through your website's contact form gets stored in a PostgreSQL database hosted on Supabase. PostgreSQL is the same database technology used by companies like Instagram, Spotify, and Netflix. It's not a spreadsheet. It's not a Google Form dumping into your inbox. It's a real database with real-time capabilities.
Here's what the database schema looks like for tracking your leads:
CREATE TABLE leads (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
client_id UUID REFERENCES clients(id),
name TEXT NOT NULL,
email TEXT,
phone TEXT NOT NULL,
service_needed TEXT,
message TEXT,
source TEXT DEFAULT 'website',
status TEXT DEFAULT 'new',
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Row Level Security so each client can only see their own leads
ALTER TABLE leads ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Clients can view own leads"
ON leads FOR SELECT
USING (client_id = auth.uid()); That last part, Row Level Security, means that even if two contractors are both using my platform, contractor A can never see contractor B's leads. The database enforces this at the query level, not just in the application code. It's the same security model banks use.
Supabase's Pro plan is $25/month and gives me 8GB of database storage, 100,000 monthly active users, and automated daily backups.
SMS Alerts: Twilio
When someone fills out the contact form on your website, you get a text message within seconds. Not an email you might check three hours later. A text. On your phone. While you're on the job.
This runs on Twilio, the same SMS infrastructure used by Uber, Airbnb, and Lyft. Here's what the serverless function looks like that sends you the alert:
// Cloudflare Worker that runs on form submission
export async function onRequestPost(context) {
const { request, env } = context;
const formData = await request.json();
// Store in Supabase
const { data, error } = await supabase
.from('leads')
.insert({
client_id: env.CLIENT_ID,
name: formData.name,
phone: formData.phone,
email: formData.email,
service_needed: formData.service,
message: formData.message
});
// Send SMS alert via Twilio
const twilioResponse = await fetch(
`https://api.twilio.com/2010-04-01/Accounts/${env.TWILIO_SID}/Messages.json`,
{
method: 'POST',
headers: {
'Authorization': `Basic ${btoa(env.TWILIO_SID + ':' + env.TWILIO_AUTH)}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
To: env.CLIENT_PHONE,
From: env.TWILIO_NUMBER,
Body: `New lead from your website!\n\n` +
`Name: ${formData.name}\n` +
`Phone: ${formData.phone}\n` +
`Service: ${formData.service}\n` +
`Message: ${formData.message}\n\n` +
`Call them back ASAP. Speed to lead wins.`
})
}
);
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' }
});
} That's about 40 lines of code to store a lead in a database and send a text message. It takes milliseconds to execute and costs fractions of a penny per message (Twilio charges roughly $0.0083 per SMS segment plus carrier fees).
Payments: Stripe
Client billing runs through Stripe. When someone signs up through my intake form, a Stripe Checkout session is created server-side:
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
allow_promotion_codes: true,
line_items: [{
price: env.STRIPE_PRICE_ID,
quantity: 1,
}],
success_url: `${env.SITE_URL}/thank-you?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${env.SITE_URL}/get-started`,
subscription_data: {
metadata: {
client_name: formData.businessName,
client_email: formData.email,
client_trade: formData.trade
}
}
}); Stripe handles PCI compliance, fraud detection, recurring billing, failed payment retries, receipt emails, and tax calculation. I don't store a single credit card number. Stripe does all of it.
Version Control: GitHub
Every line of code lives in a Git repository on GitHub. This means every change is tracked, reversible, and auditable. If I update your homepage copy on Tuesday and you don't like it, I can roll it back to Monday's version in about 10 seconds.
Here's what a typical deployment looks like from my terminal:
git add .
git commit -m "update hero section and add new service page for drain cleaning"
git push Within 45 seconds of that push, Cloudflare Pages picks it up, builds the site, and deploys it to 300+ data centers worldwide. Your updated site is live before you finish your coffee.
AI-Assisted Content: Claude API
I use Anthropic's Claude API to help generate initial content drafts, including service descriptions, FAQ answers, meta descriptions, and page copy. But here's the important part: every word gets reviewed and edited by a human (me) before it goes on your site.
The Claude Pro plan is $200/month and gives me access to the most advanced AI model available for content generation, code review, and technical problem-solving.
Analytics: Google Analytics 4
Every site ships with GA4 installed and configured. I set up event tracking for form submissions, phone number clicks, and page engagement so you can see exactly how your website is performing.
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
// Track phone clicks
document.querySelectorAll('a[href^="tel:"]').forEach(link => {
link.addEventListener('click', () => {
gtag('event', 'click_to_call', {
event_category: 'engagement',
event_label: 'phone_click'
});
});
});
</script> The Build Process, Step by Step
Here's what actually happens when you sign up.
Step 1: You Fill Out the Intake Form (5 minutes)
My intake form collects everything I need: your business name, phone number, trade, services offered, service areas, years in business, license number, photos of your work, and any websites you like for inspiration. It's a multi-step wizard with 5 steps, and most people finish it in about 5 minutes.
The form data gets submitted through Web3Forms and simultaneously stored in my Supabase database. I get an email notification and a Twilio SMS alert on my phone.
Step 2: I Generate the Site Configuration (30 minutes)
Every client site is driven by a single JSON configuration file. This is where all your business information lives:
{
"business": {
"name": "Reliable Plumbing Co",
"tagline": "Wichita's Go-To Plumber",
"phone": "316-555-0147",
"email": "info@reliableplumbingwichita.com",
"address": "Wichita, KS",
"license": "MP-2847",
"yearsInBusiness": 12,
"owner": "Mike"
},
"theme": {
"mode": "light",
"primaryColor": "#2563EB",
"accentColor": "#1E40AF",
"fontFamily": "'Inter', sans-serif"
},
"services": [
{
"name": "Drain Cleaning",
"slug": "drain-cleaning",
"description": "Slow drains and full backups handled same-day.",
"icon": "droplets"
},
{
"name": "Water Heater Repair",
"slug": "water-heater-repair",
"description": "No hot water? We repair and replace all brands.",
"icon": "flame"
}
],
"serviceAreas": ["Wichita, KS", "Derby, KS", "Andover, KS"],
"testimonials": [
{
"name": "Sarah T.",
"location": "Wichita, KS",
"rating": 5,
"text": "Mike showed up on time, gave us a fair price..."
}
],
"seo": {
"title": "Reliable Plumbing Co | Licensed Plumber in Wichita, KS",
"description": "Licensed plumber serving Wichita, Derby, Andover...",
"nearMeKeywords": true
}
} This configuration file drives the entire site. Change the phone number in one place, and it updates everywhere: the header, the hero, the footer, the contact page, the click-to-call buttons, and the schema markup.
Step 3: I Build the Site (4-8 hours)
Using the configuration file, I build out every page:
Homepage. Hero section with your business name, tagline, and a lead capture form. Services overview. Trust signals (license number, years in business, insurance status). Testimonials carousel. Service area section with an embedded map. FAQ section. Bottom CTA.
Services page. Individual cards for each service you offer, with descriptions, icons, and internal links.
About page. Your story, your team, your values. Photos of your work and your crew.
Gallery page. Before/after photos, completed projects, your equipment.
Contact page. Full lead capture form, phone number, email, service area, business hours.
Each page gets custom meta titles, descriptions, Open Graph tags for social sharing, and structured data markup (JSON-LD) so Google understands exactly what your business does and where you operate.
Here's what the structured data looks like:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Plumber",
"name": "Reliable Plumbing Co",
"image": "https://reliableplumbingwichita.com/images/hero.webp",
"telephone": "+1-316-555-0147",
"address": {
"@type": "PostalAddress",
"addressLocality": "Wichita",
"addressRegion": "KS"
},
"areaServed": [
{ "@type": "City", "name": "Wichita" },
{ "@type": "City", "name": "Derby" },
{ "@type": "City", "name": "Andover" }
],
"priceRange": "$$",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.9",
"reviewCount": "127"
}
}
</script> This schema markup helps Google display rich results, those enhanced search listings with star ratings, phone numbers, and service areas that get significantly more clicks.
Step 4: Image Optimization (1 hour)
Every image on your site gets optimized before deployment. This is one of the most overlooked steps in web design, and it has a massive impact on page speed.
Here's what I do to every image:
1. Convert to WebP format. WebP images are 25-35% smaller than JPEG at the same visual quality.
2. Resize to the actual display size. If an image displays at 800px wide, I don't upload a 3000px original.
3. Compress at 80% quality. The human eye can't tell the difference between 80% and 100% quality WebP, but the file size difference is dramatic.
4. Add explicit width and height attributes. This prevents layout shift (that annoying jump you see when images load).
5. Lazy load below-the-fold images. Images that aren't visible on the initial screen load only when the user scrolls to them.
Here's the difference this makes. An unoptimized contractor website might load 15MB of images. After my optimization pipeline, that same set of images is under 500KB. That's a 97% reduction in data transfer.
Step 5: SEO Configuration (2 hours)
This is where most DIY website builders fall short. SEO isn't just about keywords. It's about technical structure.
Every site I build includes:
- Proper heading hierarchy. One H1 per page, H2s for major sections, H3s for subsections. Screen readers and search engines use this hierarchy to understand your page structure.
- Canonical URLs. Prevents duplicate content issues when Google discovers your site through different URL paths.
- XML Sitemap. A machine-readable map of every page on your site, submitted to Google Search Console.
- robots.txt. Tells search engines which pages to crawl and which to skip.
- "Near me" optimization. Natural placement of "near me" keywords in H2 headings and body text, following the data-driven formula that matches what Google is already rewarding in local search results.
- Local schema markup. Structured data that tells Google your exact business type, location, service areas, and contact information.
- Open Graph and Twitter meta tags. So your site looks professional when shared on social media.
- Mobile-first responsive design. Google uses mobile-first indexing, meaning it ranks your site based on the mobile version, not the desktop version.
Step 6: Performance Testing (1 hour)
Before any site goes live, I run it through Google Lighthouse to measure performance, accessibility, SEO, and best practices. Here's what a typical Pixel Wrench site scores:
| Metric | Score | What It Means |
|---|---|---|
| Performance | 90+ | Site loads fast on mobile and desktop |
| Accessibility | 95+ | Usable by people with disabilities, screen readers work |
| Best Practices | 100 | Follows modern web standards, HTTPS, no console errors |
| SEO | 95+ | Properly configured for search engines |
For comparison, most contractor websites built on WordPress with a page builder score between 30 and 60 on performance. That's not a typo. Most contractor websites are failing Google's performance test.
Here's why this matters: Google has confirmed that page speed is a ranking factor. A site that scores 90+ on Lighthouse loads in under 2 seconds. A site that scores 40 takes 5-8 seconds. Every second of load time increases bounce rate by roughly 32%.
In plain English: if your website is slow, the homeowner searching for a plumber hits the back button and calls your competitor instead. You never even knew they existed.
The key performance metrics I optimize for:
- First Contentful Paint (FCP). How fast the first piece of content appears. Target: under 1.8 seconds.
- Largest Contentful Paint (LCP). How fast the main content loads. Target: under 2.5 seconds.
- Total Blocking Time (TBT). How long the page is unresponsive. Target: under 200ms.
- Cumulative Layout Shift (CLS). How much the page layout jumps around. Target: under 0.1.
Step 7: Deployment and DNS (30 minutes)
Once the site passes all tests, I deploy it:
- Push the final code to GitHub
- Cloudflare Pages automatically builds and deploys
- Configure your custom domain in Cloudflare DNS
- SSL certificate is automatically provisioned (free, courtesy of Cloudflare)
- Verify the site loads correctly on the live domain
- Submit the sitemap to Google Search Console
Your site is now live, secured with HTTPS, cached on 300+ data centers worldwide, and submitted to Google for indexing.
The Monthly Cost of Running This Stack
Here's the honest breakdown of what this infrastructure costs me per month to operate:
| Service | Cost | What It Does |
|---|---|---|
| Claude Pro (Anthropic) | $200/mo | AI-assisted content generation and code review |
| Supabase Pro | $25/mo | Database for lead storage, authentication, analytics |
| Cloudflare Workers Paid | $5/mo | Serverless functions for forms, Stripe, SMS |
| Twilio | ~$3/mo base + per SMS | SMS lead alerts to clients |
| Stripe | 2.9% + $0.30/txn | Payment processing |
| GitHub | Free | Code hosting and version control |
| Cloudflare Pages | Free | Website hosting and CDN |
| Google Analytics | Free | Traffic and engagement tracking |
| Domain registration | ~$10/year per domain | Client domain names |
| Web3Forms | Free tier | Form submission handling |
My minimum monthly overhead: ~$235/month before I've built a single site.
That doesn't include my time, which is the most expensive part. A typical site takes me 10-15 hours from intake form to live deployment. That includes the build, content writing, image optimization, SEO configuration, testing, and deployment.
Why Page Speed Matters More Than You Think
I want to spend a minute on this because it's the single biggest technical advantage of the way I build sites versus how most agencies and page builders work.
Most contractor websites built on WordPress with a page builder tend to score poorly on Google Lighthouse. It's common to see performance scores in the 30s and 40s, with pages that take 5-10 seconds to fully render on mobile. The reason is architectural: WordPress loads a PHP runtime, queries a MySQL database, renders the page server-side, then sends that HTML to the browser along with jQuery, multiple plugin scripts, Google Fonts, unoptimized images, render-blocking CSS from the theme and page builder, and tracking scripts from five different plugins. All of that adds up fast.
My Astro site ships pre-built static HTML. No database queries at runtime. No PHP. No jQuery. No plugin bloat. The HTML, CSS, and images are served directly from Cloudflare's edge network. It's the difference between ordering a meal at a restaurant (WordPress) and picking up a pre-made meal that's already at the counter (Astro on Cloudflare).
Google's own research shows that as page load time goes from 1 second to 3 seconds, the probability of bounce increases by 32%. From 1 second to 5 seconds, it increases by 90%. From 1 second to 10 seconds, it increases by 123%.
If your contractor website takes 10 seconds to load, you're losing more than half your potential customers before they even see your phone number.
Could You Build This Yourself?
Technically, yes. Everything I've described uses tools that are either free or very affordable. Astro is free. Cloudflare Pages is free. Supabase has a free tier. GitHub is free.
But here's what you'd need to learn first:
- HTML, CSS, and JavaScript. The foundational languages of the web. Expect 3-6 months of study to become competent.
- Astro framework. Component-based architecture, routing, layouts, data fetching.
- Git and GitHub. Version control, branching, merging, pull requests.
- Command line / terminal. You'll live in the terminal.
npm install,npm run build,git push, SSH, environment variables. - Cloudflare Pages and Workers. Deployment configuration, serverless functions, environment variables, DNS management.
- Supabase / PostgreSQL. Database design, SQL queries, Row Level Security policies, API integration.
- Twilio API. SMS sending, webhook handling, phone number provisioning, carrier compliance.
- Stripe API. Checkout sessions, subscription management, webhooks, PCI compliance.
- SEO. Meta tags, schema markup, XML sitemaps, Google Search Console, Core Web Vitals.
- Image optimization. WebP conversion, responsive images, lazy loading, compression.
- Responsive design. CSS media queries, mobile-first layouts, viewport testing.
- Web accessibility. WCAG guidelines, ARIA labels, heading hierarchy, contrast ratios.
- DNS management. A records, CNAME records, nameservers, SSL certificates.
- Google Analytics 4. Event tracking, conversion setup, custom dimensions.
That's 14 distinct technical disciplines. Each one takes weeks to months to learn properly. And they all have to work together.
Or you could fill out a 5-minute form and let me handle it for $49/month.
The Bottom Line
I wrote this post because I believe in transparency. I don't want you to just trust me. I want you to understand exactly what you're getting.
When you pay $49/month for a Pixel Wrench website, you're getting:
- A custom-designed website built on a modern tech stack that scores 90+ on Google Lighthouse
- Hosting on Cloudflare's global CDN (300+ data centers)
- SMS lead alerts delivered to your phone in seconds
- A real database tracking every lead
- Local SEO optimization built into every page
- Unlimited content updates
- A site that loads in under 2 seconds on mobile
You could build all of this yourself. But you'd need to invest 200+ hours learning the tools, spend $235/month on the same infrastructure I use, and then maintain it all while also running your trade business.
I'd rather you spend that time doing what you're good at. Fixing pipes, wiring panels, replacing roofs, or whatever it is you do. I'll handle the website.
Have questions about anything in this post? Contact me. I'm happy to nerd out about web performance with anyone who asks.