The fashion industry has long struggled with one fundamental challenge, how do customers know if something will look good on them before buying? Physical try-ons are time-consuming, e-commerce returns are costly, and customers are left guessing.
Today, I'm excited to share how I built “Shabab Al Yola Virtual Try-On” https://www.shababalyola.ae/virtual-try-on, an AI-powered virtual fitting solution that's changing how customers shop for traditional attire like Kandoras, Thobes, and Bishts.
The Problems
Traditional online shopping for custom tailoring has massive friction:
- Customers can't "see" how fabric looks on them
- Fitting issues lead to expensive returns
- Language barriers in regional fashion (Arabic attire)
- No way to visualize style + color combinations
The solution? An intelligent virtual try-on that lets users upload a photo, select a style and color, and generate a realistic preview, in seconds.
Here's what powers our Virtual Try-On:
Frontend: Next.js 14 + TypeScript
AI Image Generation: Gemini 2.5 Image Pro (via Runware SDK)
Image Storage: Cloudflare R2 (S3-compatible)
API Layer: Next.js Route Handlers
Validation: Zod
Architecture Overview
User uploads photo → Crop & Process → Upload to R2 → Select Style/Color → Generate AI Image → Display Result
The flow is designed for speed. I use an optimistic approach where the image uploads while the user selects their preferences, minimizing perceived latency.
Core Implementation
1. Image Upload with Presigned URLs
We use Cloudflare R2 for storage with a presigned URL pattern. This keeps our serverless function lightweight and scales infinitely:
// API Route: /api/r2/presign
const command = new PutObjectCommand({
Bucket: bucket,
Key: `faces/${Date.now()}-${crypto.randomUUID()}.${extension}`,
ContentType: mime,
});
const url = await getSignedUrl(r2(), command, { expiresIn: 300 });
// Returns uploadUrl to client, publicUrl for AI processing
Why this matters:
- No server bottleneck on file uploads
- Direct upload to R2 = faster + cheaper
- Rate limiting protects against abuse
2. AI-Powered Image Generation
Once we have the user's photo and their style preferences, we send everything to the AI:
// API Route: /api/generate
const payload = {
positivePrompt: generateTryOnPrompt(style, color),
model: "google:4@1",
numberResults: 1,
outputType: "URL",
referenceImages: [faceURL],
};
const images = await client.requestImages(payload);
3. Smart Prompt Engineering
The secret sauce is our prompt builder. It instructs the AI to preserve the user's identity while applying the selected style:
export const generateTryOnPrompt = (style: StyleOption, color: ColorOption): string => {
return `Generate a high-resolution, photorealistic fashion photoshoot
of the EXACT person from the reference image.
CRITICAL IDENTITY INSTRUCTIONS:
PRESERVE EXACT FACE: Keep user's face shape, jawline, beard style
PRESERVE BODY TYPE: Don't standardize to a fitness model body
ACCESSORIES: If wearing glasses, keep them exactly as seen
Clothing: He wears a classic ${color.value} ${style.name};
};
Key Features of AI Try-On
1- Identity Preservation: The model strictly maintains the user's facial features, body type, and accessories, no unwanted "beautification" or body shape changes.
2- Cultural Authenticity: We support regional styles: Emirati Kandora, Saudi Thobe, Qatari Thobe, each with precise collar and cuff details.
3- Fast & Responsive:
- Presigned uploads remove server bottlenecks
- Concurrent processing of style selection + image upload
- Optimized prompt length for faster AI response
4- Secure by Design
- Only whitelisted image domains allowed
- Rate limiting on upload endpoints
- Cookie-based usage limits (3 free tries/day)
Challenges I Solved
1. Image Security: Validating that user-uploaded images come from trusted sources only
2. Timeout Handling: AI image generation can take time, I implemented smart timeouts with user-friendly error messages
3. Cost Optimization: Tracking generation costs per request to manage API spend
4. Mobile-First: Ensuring the upload flow works smoothly on mobile devices
Top comments (0)