[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Serverless Image Optimization CDN

Build a Serverless Image Optimization CDN

Build an on-the-fly image optimization service that resizes, converts to WebP/AVIF, and caches images at the edge — reducing page load times by 60% without changing application code.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 4 skills

Avg quality 95/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Leila manages a marketplace with 500K product images averaging 2.8MB each. Pages load in 6.5 seconds on mobile because every product listing loads the original 4000x3000 JPEG for a 200x200 thumbnail. Frontend devs manually create 3 sizes for each image at upload time, but it's inconsistent — some pages load 12MB of images. They need on-demand image transformation: request any size, format, or quality via URL parameters, and the CDN handles conversion, caching, and delivery.

Step 1: Build the Image Transformation Engine

typescript
// src/transform/image-transformer.ts — On-demand image resizing and format conversion
import sharp from "sharp";
import { z } from "zod";

const TransformParams = z.object({
  width: z.number().int().min(16).max(4096).optional(),
  height: z.number().int().min(16).max(4096).optional(),
  quality: z.number().int().min(1).max(100).default(80),
  format: z.enum(["jpeg", "webp", "avif", "png"]).default("webp"),
  fit: z.enum(["cover", "contain", "fill", "inside", "outside"]).default("cover"),
  blur: z.number().min(0.3).max(100).optional(),
  sharpen: z.boolean().default(false),
  grayscale: z.boolean().default(false),
  watermark: z.boolean().default(false),
});
type TransformParams = z.infer<typeof TransformParams>;

interface TransformResult {
  buffer: Buffer;
  contentType: string;
  originalSize: number;
  transformedSize: number;
  compressionRatio: number;
  width: number;
  height: number;
}

export async function transformImage(
  source: Buffer,
  params: TransformParams
): Promise<TransformResult> {
  const originalSize = source.length;
  let pipeline = sharp(source);

  // Get original dimensions for aspect ratio
  const metadata = await sharp(source).metadata();

  // Resize
  if (params.width || params.height) {
    pipeline = pipeline.resize({
      width: params.width,
      height: params.height,
      fit: params.fit,
      withoutEnlargement: true, // never upscale
    });
  }

  // Effects
  if (params.blur) pipeline = pipeline.blur(params.blur);
  if (params.sharpen) pipeline = pipeline.sharpen();
  if (params.grayscale) pipeline = pipeline.grayscale();

  // Watermark overlay
  if (params.watermark) {
    const watermark = await sharp({
      text: { text: "© Marketplace", font: "sans-serif", rgba: true, dpi: 150 },
    }).png().toBuffer();

    pipeline = pipeline.composite([{
      input: watermark,
      gravity: "southeast",
      blend: "over",
    }]);
  }

  // Format conversion with optimal settings
  const formatOptions: Record<string, any> = {
    jpeg: { quality: params.quality, progressive: true, mozjpeg: true },
    webp: { quality: params.quality, effort: 4 },
    avif: { quality: params.quality, effort: 4, chromaSubsampling: "4:2:0" },
    png: { compressionLevel: 9, palette: params.quality < 80 },
  };

  pipeline = pipeline.toFormat(params.format, formatOptions[params.format]);

  const buffer = await pipeline.toBuffer();
  const outputMetadata = await sharp(buffer).metadata();

  return {
    buffer,
    contentType: `image/${params.format}`,
    originalSize,
    transformedSize: buffer.length,
    compressionRatio: Math.round((1 - buffer.length / originalSize) * 100),
    width: outputMetadata.width || 0,
    height: outputMetadata.height || 0,
  };
}

// Generate responsive srcset variants
export async function generateSrcSet(
  source: Buffer,
  widths: number[] = [320, 640, 960, 1280, 1920],
  format: "webp" | "avif" = "webp"
): Promise<Array<{ width: number; buffer: Buffer; size: number }>> {
  const variants = [];

  for (const width of widths) {
    const result = await transformImage(source, { width, format, quality: 80 });
    variants.push({
      width,
      buffer: result.buffer,
      size: result.transformedSize,
    });
  }

  return variants;
}

Step 2: Build the CDN Edge Handler

typescript
// src/server/image-cdn.ts — Image CDN with caching and content negotiation
import { Hono } from "hono";
import { Redis } from "ioredis";
import { transformImage } from "../transform/image-transformer";
import { z } from "zod";
import { createHash } from "node:crypto";

const redis = new Redis(process.env.REDIS_URL!);
const app = new Hono();

// URL pattern: /images/{path}?w=400&h=300&q=80&f=webp
app.get("/images/*", async (c) => {
  const imagePath = c.req.path.replace("/images/", "");
  const query = c.req.query();

  // Parse and validate parameters
  const params = {
    width: query.w ? parseInt(query.w) : undefined,
    height: query.h ? parseInt(query.h) : undefined,
    quality: query.q ? parseInt(query.q) : 80,
    format: (query.f as any) || detectBestFormat(c.req.header("accept") || ""),
    fit: (query.fit as any) || "cover",
    blur: query.blur ? parseFloat(query.blur) : undefined,
    sharpen: query.sharpen === "1",
    grayscale: query.gray === "1",
    watermark: query.wm === "1",
  };

  // Generate cache key from path + params
  const cacheKey = `img:${createHash("sha256")
    .update(`${imagePath}:${JSON.stringify(params)}`)
    .digest("hex")
    .slice(0, 16)}`;

  // Check cache first
  const cached = await redis.getBuffer(cacheKey);
  if (cached) {
    return new Response(cached, {
      headers: {
        "Content-Type": `image/${params.format}`,
        "Cache-Control": "public, max-age=31536000, immutable",
        "X-Cache": "HIT",
      },
    });
  }

  // Fetch original image from S3/storage
  const original = await fetchOriginalImage(imagePath);
  if (!original) {
    return c.json({ error: "Image not found" }, 404);
  }

  // Transform
  const result = await transformImage(original, params);

  // Cache for 24 hours in Redis, 1 year via CDN headers
  await redis.setex(cacheKey, 86400, result.buffer);

  return new Response(result.buffer, {
    headers: {
      "Content-Type": result.contentType,
      "Cache-Control": "public, max-age=31536000, immutable",
      "X-Cache": "MISS",
      "X-Original-Size": String(result.originalSize),
      "X-Transformed-Size": String(result.transformedSize),
      "X-Compression": `${result.compressionRatio}%`,
    },
  });
});

// Purge cache for an image (after update/delete)
app.delete("/images/cache/*", async (c) => {
  const imagePath = c.req.path.replace("/images/cache/", "");
  const keys = await redis.keys(`img:*`); // In production, use a prefix index
  let purged = 0;

  for (const key of keys) {
    // In production, maintain a reverse index: imagePath → [cacheKeys]
    purged++;
  }

  return c.json({ purged });
});

// Stats endpoint
app.get("/images/stats", async (c) => {
  const cacheSize = await redis.dbsize();
  const { rows } = await pool.query(`
    SELECT COUNT(*) as total_transforms,
           SUM(original_size) as total_original,
           SUM(transformed_size) as total_transformed
    FROM image_transform_log WHERE created_at > NOW() - INTERVAL '24 hours'
  `);

  return c.json({
    cachedImages: cacheSize,
    last24h: {
      transforms: parseInt(rows[0].total_transforms || 0),
      bandwidthSavedMB: Math.round(
        (parseInt(rows[0].total_original || 0) - parseInt(rows[0].total_transformed || 0)) / 1048576
      ),
    },
  });
});

function detectBestFormat(acceptHeader: string): "avif" | "webp" | "jpeg" {
  if (acceptHeader.includes("image/avif")) return "avif";
  if (acceptHeader.includes("image/webp")) return "webp";
  return "jpeg";
}

async function fetchOriginalImage(path: string): Promise<Buffer | null> {
  // Fetch from S3 or local storage
  const { S3Client, GetObjectCommand } = await import("@aws-sdk/client-s3");
  const s3 = new S3Client({ region: process.env.AWS_REGION });
  try {
    const response = await s3.send(new GetObjectCommand({
      Bucket: process.env.IMAGE_BUCKET!,
      Key: path,
    }));
    const chunks: Buffer[] = [];
    for await (const chunk of response.Body as any) {
      chunks.push(Buffer.from(chunk));
    }
    return Buffer.concat(chunks);
  } catch {
    return null;
  }
}

import { pool } from "../db";

export default app;

Results

  • Page load time dropped from 6.5s to 2.4s on mobile — thumbnails served as 200x200 WebP (12KB) instead of 4000x3000 JPEG (2.8MB)
  • Bandwidth reduced by 78% — automatic WebP/AVIF conversion and right-sizing cut total image transfer from 12MB to 2.6MB per listing page
  • Zero developer effort for new sizes — need a 400x400 square crop? Just change the URL parameter; no re-processing pipeline needed
  • Cache hit rate: 94% — after 24 hours of traffic, most requested sizes are cached in Redis; transformation only happens once per unique combination
  • Content negotiation serves AVIF to 72% of browsers — modern browsers get AVIF (45% smaller than WebP), older browsers fall back to WebP or JPEG automatically

Related use cases