Fusing Two Parents Into One Face: Building an Identity-Preserving AI Baby Generator on Cloudflare Functions
BabyGlimpse takes a photo of each parent and generates AI baby portraits across three age stages (newborn, toddler, child). The interesting problem wasn't "call an image model" — it was fusing two separate faces into one coherent identity, inside the wall-clock limits of a serverless function.
The model: PuLID, not a generic text-to-image call
Describing both faces in a text prompt doesn't work — text can't carry facial identity with any fidelity. This pipe uses fal-ai/pulid, an identity-preserving diffusion model that accepts actual reference images. Both parent photos go in as reference_images, and id_mix tells the model to blend the two identities into one new face instead of copying either one:
const submit = await fetch("https://queue.fal.run/fal-ai/pulid", {
method: "POST",
headers: { Authorization: "Key " + env.FAL_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
reference_images: [{ image_url: refA }, { image_url: refB }],
prompt, image_size: "square_hd",
num_inference_steps: 8, guidance_scale: 1.2,
id_scale: 0.8, id_mix: true, mode: "fidelity",
negative_prompt: NEG, seed,
}),
});
fal-ai/pulid runs on SDXL-Lightning, tuned for very few steps (4-8) — more steps don't improve a Lightning checkpoint, they just burn time. id_scale: 0.8 sets how strongly the result must resemble both reference identities versus the text prompt; mode: "fidelity" biases PuLID toward keeping recognizable features from both parents.
The finishing pass: a second model call kills the "AI plastic" look
An 8-step Lightning output is identity-accurate but lacks fine skin texture. Rather than push more steps into a model tuned for few steps, the output goes through fal-ai/clarity-upscaler as a separate pass with a real (non-Lightning) CFG — low creativity, high resemblance, so it enhances texture without drifting from the identity PuLID already locked in:
const submit = await fetch("https://queue.fal.run/fal-ai/clarity-upscaler", {
method: "POST",
headers: { Authorization: "Key " + env.FAL_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
image_url: imageUrl, upscale_factor: 2,
creativity: 0.3, resemblance: 1.4,
guidance_scale: 4, num_inference_steps: 18,
negative_prompt: "cartoon, cgi, 3d render, plastic skin, waxy skin, ...",
}),
});
If this pass fails or times out, the code falls back to the un-upscaled base image — a paid job should never come back empty because a secondary enhancement step had a bad day.
Why the browser drives the loop, not the server
fal.run is an async queue: submit a job, get a status_url/response_url pair, poll until status === "COMPLETED". A full batch (three age stages, several images each, each with its own upscale pass) can take minutes — too long for one serverless invocation to own.
So the client owns the top-level loop. Each /api/generate call handles exactly one age stage; the browser calls it three times in sequence (newborn → toddler → child), updating a progress bar between calls. Each Cloudflare Pages Function invocation only has to survive polling for one stage's images, keeping every request inside the platform's CPU/time budget instead of babysitting one giant multi-minute request.
Payment: a signed grant instead of a database
There's no database here. After PayPal capture succeeds server-side, capture-order.js issues an HMAC-signed grant — a base64 JSON payload {o, sku, n, exp} plus a SHA-256 signature via the Web Crypto API:
async function sign(secret, payload) {
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload));
return btoa(String.fromCharCode(...new Uint8Array(sig))).replace(/=+$/, "");
}
/api/generate re-derives the same signature and rejects the request if it doesn't match, or if exp has passed (grants last 6 hours). That's the entire access-control layer for a paid API calling a metered third-party image model — no session store, no order table, just a signed token the browser carries between requests. Pricing itself lives server-side in one TIERS object; the client never gets to influence what PayPal actually charges.
Top comments (1)
I found the approach of using
fal-ai/pulidwithid_mixto blend the two parent faces into one coherent identity particularly interesting, as it addresses the challenge of preserving facial identity in the generated images. The use of a second model,fal-ai/clarity-upscaler, to enhance the skin texture and remove the "AI plastic" look is also a great touch. I've worked with similar models in the past, and I can appreciate the trade-offs involved in tuning theid_scaleandmodeparameters to achieve the right balance between fidelity and feature preservation. What considerations did you take into account when deciding to use a client-driven loop versus a server-side implementation, and how did you optimize the Cloudflare Pages Function invocations to stay within the platform's CPU/time budget?