Most website builders assume their users are at least somewhat tech-savvy. Squarespace expects you to understand layout concepts. WordPress expects you to manage plugins. Even Wix assumes you know what a "section" is.
French artisans — plumbers, electricians, carpenters, roofers — generally do not care about any of that. They want a website that shows up on Google when someone searches for their trade in their town. That is it. We built Mon Site Artisan specifically for this audience, and the engineering constraints were surprisingly interesting.
The Design Constraint: Zero Learning Curve
Our target user profile:
- Age 35-55
- Uses a smartphone daily but a computer rarely
- Has never edited HTML, CSS, or any code
- Wants a website in under 15 minutes
- Needs it to rank locally on Google
This means no drag-and-drop editors, no template marketplaces, no widget libraries. Instead, we built a wizard-based flow that asks questions and generates a complete website from the answers.
The Wizard Architecture
class SiteWizard {
private array $steps = [
1 => "trade_selection",
2 => "business_info",
3 => "service_area",
4 => "photo_upload",
5 => "review_and_publish"
];
public function processStep(int $step, array $data): array {
return match($step) {
1 => $this->handleTradeSelection($data),
2 => $this->handleBusinessInfo($data),
3 => $this->handleServiceArea($data),
4 => $this->handlePhotoUpload($data),
5 => $this->handlePublish($data),
};
}
private function handleTradeSelection(array $data): array {
$trade = $data["trade"];
// Pre-load trade-specific content templates
$template = TradeTemplates::get($trade);
return [
"next_step" => 2,
"prefilled" => [
"services" => $template->defaultServices(),
"seo_keywords" => $template->suggestedKeywords(),
"color_scheme" => $template->recommendedColors()
]
];
}
}
Step 1 is the key differentiator. When a plumber selects "plombier" (plumber), the system pre-fills everything with plumbing-specific defaults:
- Service descriptions ("Depannage plomberie", "Installation sanitaire", "Detection fuite")
- Color scheme (blue tones — industry convention)
- SEO keywords ("plombier + [their city]")
- Schema markup (
Plumbertype) - Stock photos (tools, pipes, bathroom installations)
The artisan only needs to add their name, phone number, city, and optionally upload their own photos.
Trade-Specific Template Engine
We maintain templates for 25 trades. Each template includes:
class PlumberTemplate extends TradeTemplate {
public string $trade = "plombier";
public string $schema_type = "Plumber";
public function defaultServices(): array {
return [
["name" => "Depannage plomberie", "description" => "Intervention rapide pour fuites, canalisations bouchees, et pannes."],
["name" => "Installation sanitaire", "description" => "Pose de douche, baignoire, WC, lavabo et robinetterie."],
["name" => "Chauffe-eau", "description" => "Installation, remplacement et entretien de chauffe-eau."],
["name" => "Detection de fuites", "description" => "Recherche et reparation de fuites non visibles."],
];
}
public function suggestedKeywords(): array {
return ["plombier", "depannage plomberie", "plombier urgence", "fuite eau"];
}
public function recommendedColors(): array {
return ["primary" => "#1a73e8", "secondary" => "#0d47a1", "accent" => "#4fc3f7"];
}
public function schemaMarkup(array $businessData): array {
return [
"@context" => "https://schema.org",
"@type" => "Plumber",
"name" => $businessData["company_name"],
"telephone" => $businessData["phone"],
"areaServed" => $businessData["service_area"],
"address" => [
"@type" => "PostalAddress",
"addressLocality" => $businessData["city"],
"addressCountry" => "FR"
]
];
}
}
Static Site Generation: Why Not WordPress
For this use case, WordPress is overkill. An artisan website has 3-5 pages that change once a year. We generate static HTML files:
class StaticSiteGenerator {
public function generate(array $siteData): string {
$outputDir = "/sites/{$siteData["slug"]}/";
$pages = [
"index.html" => $this->renderHome($siteData),
"services/index.html" => $this->renderServices($siteData),
"contact/index.html" => $this->renderContact($siteData),
"mentions-legales/index.html" => $this->renderLegal($siteData),
"sitemap.xml" => $this->renderSitemap($siteData),
"robots.txt" => $this->renderRobots($siteData),
];
foreach ($pages as $path => $content) {
file_put_contents($outputDir . $path, $content);
}
// Generate optimized images
$this->processImages($siteData["photos"], $outputDir . "images/");
return $outputDir;
}
}
Benefits of static generation:
- Speed: Sub-50ms TTFB. No database queries, no PHP execution on page load.
- Security: No WordPress vulnerabilities, no plugin updates, no admin panel to hack.
- Hosting cost: Pennies per month. Static files on shared hosting behind Cloudflare.
- Reliability: Nothing to crash. HTML files just work.
Local SEO: The Core Value Proposition
The entire point of the product is Google visibility. Every generated site includes:
- Geo-targeted title tags: "Plombier a Lyon 7eme - [Company Name]"
- LocalBusiness schema: With exact coordinates from the French geocoding API
- City-specific content: Auto-generated paragraphs mentioning neighborhoods and surrounding towns
- Google Business Profile integration: Instructions to claim and link their GBP listing
- NAP consistency: Name, Address, Phone formatted identically across all pages
function generateLocalContent($trade, $city, $department) {
$nearbyTowns = fetchNearbyTowns($city, radius: 20);
$content = "Votre {$trade} a {$city} ({$department}) intervient ";
$content .= "egalement dans les communes voisines : ";
$content .= implode(", ", array_slice($nearbyTowns, 0, 8));
$content .= ". Contactez-nous pour un devis gratuit.";
return $content;
}
Image Optimization Pipeline
Artisans upload photos from their phone — often 4MB+ JPEG files straight from the camera. Our pipeline:
function optimizeImage($inputPath, $outputDir) {
$image = imagecreatefromjpeg($inputPath);
$width = imagesx($image);
$height = imagesy($image);
$sizes = [
"large" => 1200,
"medium" => 800,
"thumb" => 400
];
foreach ($sizes as $name => $targetWidth) {
if ($width > $targetWidth) {
$ratio = $targetWidth / $width;
$newHeight = (int)($height * $ratio);
$resized = imagecreatetruecolor($targetWidth, $newHeight);
imagecopyresampled($resized, $image, 0, 0, 0, 0,
$targetWidth, $newHeight, $width, $height);
// Save as WebP for modern browsers
imagewebp($resized, "{$outputDir}/{$name}.webp", 80);
// JPEG fallback
imagejpeg($resized, "{$outputDir}/{$name}.jpg", 82);
imagedestroy($resized);
}
}
imagedestroy($image);
}
A 4MB phone photo becomes a 60KB WebP thumbnail. Page weight for a typical artisan site: under 500KB total.
Results
After 6 months:
- Average time to create a site: 8 minutes
- Average PageSpeed score: 97/100
- Most sites appear in local search results within 4-6 weeks
- Zero support tickets about "how to edit" — because the wizard handles everything
The lesson: sometimes the best technical decision is removing options, not adding them.
Create a professional artisan website in under 15 minutes at mon-site-artisan.net
Top comments (0)