When building Zyvop—a platform designed to help developers write once and distribute everywhere—we realized a major friction point for writers: finding a good cover image.
Developers want to write code and explain technical concepts, not spend 30 minutes browsing Unsplash for a generic stock photo of a laptop. We wanted to solve this by automatically generating beautiful, context-aware cover images for every post using AI.
Here is exactly how we built a dynamic image generation pipeline using NestJS, Hugging Face's FLUX model, and Sharp for image optimization.
The Architecture
We needed a system that was fast, reliable, and produced high-quality images. We settled on the following stack:
Backend: NestJS
Model: Black Forest Labs'
FLUX.1-schnell(via Hugging Face Inference API)Image Processing:
sharp(for resizing and converting to WebP)
The schnell variant of FLUX is incredibly fast and perfect for on-the-fly generation without making the user wait 30 seconds for an image.
Step 1: The Prompt Engineering
The first step was taking a user's blog post title and turning it into a prompt that reliably produces clean, modern graphics.
In our ImageGenerationService, we built a simple prompt generator:
buildPromptFromTitle(title: string, keywords?: string[]): string {
const keywordStr = keywords?.length ? `, related to ${keywords.slice(0, 3).join(', ')}` : '';
return `Simple, clean modern blog cover image. Professional and elegant. Topic: "${title}"${keywordStr}. With best background but remember this image has text`;
}
The trick here is keeping the prompt constrained. By appending "Simple, clean modern blog cover image," we prevent the AI from generating overly chaotic or photorealistic images that distract from the article title.
Step 2: Hitting the Hugging Face API
Next, we make a POST request to the Hugging Face router. NestJS makes it easy to inject the ConfigService to securely manage our API keys.
async generateImage(prompt: string): Promise<Buffer | null> {
const modelEndpoint = 'https://router.huggingface.co/hf-inference/models/black-forest-labs/FLUX.1-schnell';
const response = await fetch(modelEndpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
inputs: prompt,
parameters: {
width: 1024,
height: 576, // Standard 16:9 aspect ratio
seed: Math.floor(Math.random() * 1000000), // Randomize for variety
},
}),
});
if (!response.ok) {
throw new Error(`HF API error: ${response.status}`);
}
const arrayBuffer = await response.arrayBuffer();
return Buffer.from(arrayBuffer);
}
We specifically request a 1024x576 image to maintain a standard 16:9 aspect ratio, which is ideal for Open Graph cards (Twitter/LinkedIn previews) and blog headers.
Step 3: Optimization with Sharp
Raw AI-generated images can be massive (often several megabytes). Serving a 3MB image as a blog cover is terrible for Web Vitals and user experience.
Before saving the image, we run it through sharp to resize it (upscaling slightly to 1600x900 for high-DPI displays) and convert it to a highly compressed WebP format.
import * as sharp from 'sharp';
async generateAndSave(prompt: string): Promise<{ key: string; publicUrl: string } | null> {
const imageBuffer = await this.generateImage(prompt);
if (!imageBuffer) return null;
const now = Date.now();
const rand = Math.random().toString(36).slice(2);
const key = `posts/covers/ai-${now}-${rand}.webp`;
// ⚡ Process with sharp for optimization
const optimizedBuffer = await sharp(imageBuffer)
.resize(1600, 900, { fit: 'cover' })
.webp({ quality: 85 }) // Compress to WebP!
.toBuffer();
// Save to local filesystem (or S3)
const uploadsRoot = join(process.cwd(), 'uploads');
const absPath = join(uploadsRoot, key);
await fs.writeFile(absPath, optimizedBuffer);
const publicUrl = `${this.config.get('PUBLIC_BASE_URL')}/uploads/${key}`;
return { key, publicUrl };
}
By converting to WebP with 85% quality, we reduced the average cover image payload from ~2.5MB down to ~150KB—a 94% reduction in file size!
The Result
Now, when a developer writes an article on Zyvop, they click one button and instantly receive a beautiful, optimized, context-aware cover image ready to be syndicated to Dev.to and Hashnode.
If you are building an AI feature into your backend, I highly recommend the combination of NestJS, Hugging Face (specifically FLUX), and Sharp. It's an incredibly robust pipeline.
Want to try it out? Come write your next article on ZyVOP.com and let our AI generate your cover for you.
Comments (0)
Login to post a comment.