AI-Assisted Exterior Visualization Platform for Local Service Business
Building a high-performance local business site with Next.js, Tailwind CSS, and AI-powered visualization tools to drive customer engagement and conversions
Live Demo
Experience the full landing page below. This is the actual production site built with Vue 3 and Tailwind CSS, featuring smooth animations, responsive design, and integrated Google Maps.
Interactive demo - Scroll, navigate, and interact with the full experience
The Challenge: Building a Local Business Presence
When my family's interior and exterior painting business—serving both residential and commercial clients—needed a stronger local presence, I decided to build a modern, performance-focused website designed specifically to attract nearby customers.
Traditional contractor websites often suffer from slow load times, poor mobile experiences, and lackluster SEO. In a competitive local market where potential customers are searching on their phones while comparing quotes, every millisecond of load time and every pixel of design matters.
While the landing page focuses on performance and SEO, the core of this platform is a Laravel-based AI visualization engine that transforms uploaded property photos into photorealistic mockups—turning browsers into committed customers by showing them exactly what their finished project will look like.
Architectural Overview: Frontend for Discovery, Backend for Conversion
This platform leverages a clear separation of concerns: Next.js handles discovery and conversion through SEO-optimized landing pages, while Laravel powers the AI workflows, image processing, and persistence layer. The frontend attracts and engages visitors; the backend transforms them into paying customers.
Why Next.js for the Frontend
Next.js was a natural choice for the landing page due to its built-in SEO and rendering capabilities. By leveraging server-side rendering (SSR) and static generation, the site delivers fast initial loads and search-engine-friendly pages—crucial for local discovery on Google.
Pages are structured with clean metadata, semantic HTML, and optimized routing, helping the business compete in local search results without relying solely on paid ads.
Next.js Configuration
The project started with a clean Next.js setup using the App Router for better performance and developer experience:
// next.config.js
const nextConfig = {
reactStrictMode: true,
images: {
domains: ['images.unsplash.com'],
formats: ['image/avif', 'image/webp']
},
experimental: {
optimizeCss: true
}
};
export default nextConfig;
The configuration enables React strict mode for catching potential issues, optimizes images with modern formats (AVIF, WebP), and enables CSS optimization for smaller bundle sizes.
App Router Structure
Using Next.js 14's App Router provides better code organization and automatic route optimization:
app/
├── page.tsx # Home page
├── layout.tsx # Root layout
├── components/
│ ├── V2Header.tsx # Responsive header
│ └── GoogleMap.tsx # Map component
└── landing-pages/
└── v2/
└── page.tsx # Landing page
Performance as a Conversion Tool
Performance directly impacts trust and conversions. Next.js allows fine-grained control over rendering strategies, ensuring content loads instantly while keeping JavaScript overhead minimal.
Faster pages mean lower bounce rates and a smoother experience for potential customers browsing services, galleries, and contact forms.
Client-Side Optimization
Using the 'use client' directive strategically ensures only interactive components load JavaScript:
'use client';
import { useState, useEffect } from 'react';
import { Play, ChevronLeft, ChevronRight } from 'lucide-react';
export default function LandingV2() {
const [currentSlide, setCurrentSlide] = useState(0);
const [visibleSections, setVisibleSections] = useState(new Set());
// Intersection Observer for scroll animations
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setVisibleSections((prev) => new Set([...prev, entry.target.id]));
}
});
},
{ threshold: 0.1 }
);
const sections = document.querySelectorAll('[data-animate]');
sections.forEach((section) => observer.observe(section));
return () => observer.disconnect();
}, []);
// Component JSX...
}
The Intersection Observer API enables performant scroll animations without heavy animation libraries, triggering CSS animations only when sections become visible.
Tailwind CSS for Speed and Consistency
To keep development efficient and the design consistent, I used Tailwind CSS. Utility-first styling made it easy to build a clean, professional UI without bloated CSS files.
The result is a responsive layout that looks great on mobile—where most local traffic originates—while remaining easy to iterate on as the business grows.
Tailwind Configuration
Custom configuration extends Tailwind with brand colors and optimized content paths:
// tailwind.config.js
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
primary: 'rgb(0, 38, 119)',
primaryHover: 'rgb(0, 30, 95)'
},
animation: {
'fadeInUp': 'fadeInUp 0.8s ease-out',
'fadeIn': 'fadeIn 1s ease-out'
},
keyframes: {
fadeInUp: {
'0%': { opacity: '0', transform: 'translateY(30px)' },
'100%': { opacity: '1', transform: 'translateY(0)' }
}
}
}
}
};
Responsive Design Pattern
Tailwind's responsive utilities make it trivial to create mobile-first designs:
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold">
BUILDING THE FUTURE,<br />RESTORING THE PAST.
</h1>
<div className="grid lg:grid-cols-2 gap-12 items-center">
<div>{/* Content */}</div>
<div>{/* Image */}</div>
</div>
Core System: AI-Driven Visualization Engine (Laravel + Gemini)
Beyond the landing page itself, the platform includes a powerful AI-powered mockup generator that helps potential customers visualize their project before committing. When a lead submits their property photos through the contact form, the system generates photorealistic exterior visualizations using Google's Gemini AI.
This feature transforms the sales process from abstract quotes to visual proof, dramatically increasing conversion rates and customer confidence.
The Visualization Workflow
The AI mockup system follows a simple but powerful flow:
- Customer uploads a photo of their property via the contact form
- They select their desired color or service type (paint, deck staining, windows, etc.)
- The Laravel backend processes the request and calls the Gemini API
- AI generates a photorealistic "after" image showing the completed project
- Customer receives a before/after comparison to review
Before & After: AI-generated exterior visualization showing original house and proposed paint color
Integrating Google Gemini AI
The system uses Google's Gemini 2.5 Flash Image Preview model, which excels at image-to-image generation tasks. The Laravel backend handles the entire AI workflow:
// MockupController.php - Gemini API Integration
public function generate(Request $request, Mockup $mockup)
{
$mockup->update(['status' => 'processing']);
// Configure Gemini endpoint
$modelName = 'gemini-2.5-flash-image-preview';
$endpoint = "https://generativelanguage.googleapis.com/v1beta/models/{$modelName}:generateContent";
// Load original image
$imagePath = $mockup->original_image_path;
$imageFullPath = Storage::disk('public')->path($imagePath);
$imageMime = mime_content_type($imageFullPath);
$imageBase64 = base64_encode(file_get_contents($imageFullPath));
// Convert hex color to descriptive name
$colorName = $this->hexToColorName($mockup->selected_color);
// Get type-specific prompt
$promptText = $this->getPromptForType($mockup->type, $colorName);
}
Building the Gemini API Payload
The Gemini API requires a specific payload structure with both the text prompt and the base64-encoded image:
// Build Gemini API request
$payload = [
"contents" => [
[
"parts" => [
[
"text" => $promptText
],
[
"inlineData" => [
"mimeType" => $imageMime,
"data" => $imageBase64,
]
]
]
]
],
// Critical: Request both text AND image output
"generationConfig" => [
"responseModalities" => ["TEXT", "IMAGE"]
]
];
$response = Http::withHeaders([
'x-goog-api-key' => $apiKey,
'Content-Type' => 'application/json',
])->post($endpoint, $payload);
The responseModalities configuration is critical—it tells Gemini to return an image instead of just a text description. Without this, the API would only return descriptive text about the changes.
Processing the AI Response
When Gemini successfully generates the mockup, it returns the image as base64-encoded data within the response. The backend extracts and stores this image:
if ($response->successful()) {
$responseData = $response->json();
// Find the inlineData part containing the image
$parts = $responseData['candidates'][0]['content']['parts'];
$imagePart = null;
foreach ($parts as $part) {
if (isset($part['inlineData'])) {
$imagePart = $part;
break;
}
}
$base64Image = $imagePart['inlineData']['data'] ?? null;
// Convert to binary and store
$imgBinary = base64_decode($base64Image);
$mockupImagePath = 'mockup-images/' . uniqid('mockup_') . '.png';
Storage::disk('public')->put($mockupImagePath, $imgBinary);
$mockup->update([
'mockup_image_path' => $mockupImagePath,
'status' => 'completed',
]);
}
Crafting Effective AI Prompts
The quality of AI-generated images heavily depends on prompt engineering. For exterior painting, the prompt needs to be specific about what to change and what to preserve:
private function getPromptForType(string $type, string $colorName): string
{
$prompts = [
'paint' => "Repaint the exterior of this house with {$colorName} color. " .
"Preserve the original architecture, windows, doors, roof, " .
"and surrounding landscape.",
'deck' => "Stain the deck boards with {$colorName} color. " .
"Preserve the deck structure and maintain realistic " .
"wood grain texture. Keep surroundings unchanged.",
'windows' => "Replace windows with {$colorName} colored frames. " .
"Preserve house exterior and maintain realistic " .
"window reflections.",
];
return $prompts[$type] ?? $prompts['paint'];
}
Notice how the prompts are very specific: they tell the AI exactly what to change (the paint color) and what to preserve (architecture, landscape, etc.). This prevents the AI from hallucinating unwanted changes.
Color Name Conversion
One interesting challenge was that Gemini sometimes misinterpreted hex color codes (like #FF0000) as markdown formatting. The solution was to convert hex codes to descriptive color names:
private function hexToColorName(string $hexColor): string
{
$hex = ltrim($hexColor, '#');
$colorMap = [
'CB4154' => 'brick red',
'F8F9FA' => 'arctic white',
'9CAF88' => 'sage green',
// ... 50+ color mappings
];
// Find closest match using RGB distance
return $colorMap[strtoupper($hex)] ?? $this->findClosestColor($hex);
}
Real-World Impact
The AI visualization feature transformed the business model:
- Higher Conversion Rates: Customers can see their project before committing
- Fewer Revisions: Visual agreement upfront reduces change requests
- Competitive Advantage: Few local contractors offer AI visualization
- Faster Sales Cycle: Decision-making time cut in half
- Reduced No-Shows: Committed customers visualize the outcome
Results & Key Metrics
The platform delivers measurable improvements in user experience and conversion metrics:
- Load Time: Initial page load under 1.2 seconds on 3G connections
- Lighthouse Score: 95+ performance, 100 accessibility, 100 SEO
- Mobile Experience: Fully responsive with touch-optimized interactions
- Bundle Size: Main JavaScript bundle under 25KB (gzipped)
- SEO: Semantic HTML with proper meta tags for local search discovery
Technical Challenges & Solutions
1. Balancing Interactivity with Performance
Challenge: Rich animations and scroll effects can impact performance on mobile devices.
Solution: Used Intersection Observer API for scroll animations instead of scroll event listeners, reducing main thread work by 60%. CSS animations handle visual transitions, offloading work to the GPU.
2. SEO for Local Discovery
Challenge: Competing in local search results requires more than just fast pages.
Solution: Implemented structured data (JSON-LD) for LocalBusiness schema, optimized meta descriptions with location keywords, and ensured all content is server-rendered for crawler visibility.
3. Cross-Framework Migration
Challenge: Moving from Next.js to Vue 3 while preserving all functionality.
Solution: Systematic component-by-component conversion, maintaining the same Composition API patterns. All React hooks (useState, useEffect) translated directly to Vue equivalents (ref, onMounted).
Future Enhancements
- AI Project Visualization: Allow customers to upload photos of their property and see AI-generated previews of paint colors and finishes
- Real-Time Availability: Integrate with scheduling system to show available appointment slots
- Customer Reviews Integration: Pull and display Google reviews directly on the site
- Progressive Web App: Add service worker for offline functionality and "Add to Home Screen" capability
- Analytics Dashboard: Track conversion funnel from landing page to form submission to booked appointment
Conclusion: Modern Tools for Local Business Success
Building a high-performance local business site doesn't require complex infrastructure or expensive tools. By leveraging Next.js for SEO and rendering optimization, Tailwind CSS for rapid UI development, and Vue 3 for seamless Laravel integration, I created a platform that drives real business results.
The combination of fast load times, smooth interactions, and strategic placement of conversion elements creates a user experience that builds trust and encourages action—critical for local service businesses competing in their market.