When generating images with modern AI models, one of the most frustrating limitations is character and composition drift. You enter a prompt, get an interesting portrait, change one word to alter the outfit or background, and the face completely changes.

To solve this, I designed and built Photo2Photo AI — a browser-based image-to-image studio focused specifically on consistent character rendering, sketch-to-photo conversion, and high-fidelity photo restyling without losing the subject's identity.

In this article, I want to walk through the system architecture, the dual-tier AI inference pipeline, and the engineering decisions behind edge-rendering on Cloudflare Pages.

🏗️ 1. Architecture Overview

Here is the high-level architecture:

[Browser Client] │ (Client-side WebP compression < 4MB) ▼ [Cloudflare Pages Edge Network] ├─ Static Assets & ISR Pages ├─ Turnstile Human Verification └─ Cloudflare D1 (Serverless SQLite Ledger) & Cloudflare R2 (Object Storage) │ ▼ [Multi-Engine Inference Gateway] ├─ Fast Tier (ByteDance Seedream 5.0 Flash) — ~12s rapid turnaround └─ Studio Pro (FLUX.1 Kontext Pro) — High commercial depth & 4K upscaling 

Why Cloudflare Pages + Next.js 15?

We opted for Next.js 15 compiled via @cloudflare/next-on-pages. This gives us:

  1. Zero Cold-Start Worldwide: Cloudflare's global edge serves SSR and API routes within 50ms from almost any country.
  2. Built-in Bot Protection: Cloudflare Turnstile validates human interaction seamlessly without annoying CAPTCHA puzzles.
  3. Decentralized Storage: Uploaded reference photos land directly in Cloudflare R2 with automatic lifecycle expiration rules (free user outputs purge after 24 hours).

⚡ 2. The Core Challenge: Preserving Identity in Image-to-Image

Traditional text-to-image models hallucinate geometry from scratch. To achieve true identity lock:

  • We allow users to provide up to 4 reference photos of the same subject.
  • The reference embeddings are mapped to spatial cross-attention layers.
  • We support dedicated operational presets for distinct real-world workflows:

🚀 3. Dual-Tier Inference: Balancing Speed vs. Commercial Quality

Different users have fundamentally different latency and quality budgets:

Tier Engine / Model Latency Credit Cost Best For
Fast ByteDance Seedream 5.0 Flash ~10–12 sec 1 Credit Quick drafts, concept validation, social posts
Studio Pro FLUX.1 Kontext Pro ~20–25 sec 3 Credits Commercial advertising, realistic skin textures, 4K export

By decoupling the pipeline into Fast and Studio Pro, users can iterate rapidly on the Fast tier before spending compute on the Studio Pro engine for final 4K renders.

🔒 4. Client-Side WebP Compression: Never Upload Heavy Payloads

Rather than letting users upload 25MB RAW or uncompressed PNG files to the server (clogging bandwidth and slowing upload queues), we process all incoming images in the browser before dispatching:

// Compress image client-side to WebP (max edge 2048px, quality 0.88) export async function compressImageToWebP(file: File): Promise<Blob> { const img = new Image(); img.src = URL.createObjectURL(file); await img.decode(); const maxEdge = 2048; let { width, height } = img; if (width > maxEdge || height > maxEdge) { if (width > height) { height = Math.round((height * maxEdge) / width); width = maxEdge; } else { width = Math.round((width * maxEdge) / height); height = maxEdge; } } const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const ctx = canvas.getContext("2d")!; ctx.drawImage(img, 0, 0, width, height); return new Promise((resolve) => { canvas.toBlob((blob) => resolve(blob!), "image/webp", 0.88); }); } 

This simple optimization reduced median upload times from 4.2 seconds down to 320ms.

🎁 5. No Subscription & Free Trial Philosophy

We decided against recurring monthly subscriptions. You can try 3 generations for free without even signing up:

Feedback and technical critiques are very welcome in the comments!