Use Cases (अपडेट: 2/6/2026)

Claude Code से image processing: Sharp, Canvas, WebP/AVIF और upload validation

Claude Code से safe image processing बनाएं: Sharp, Canvas, EXIF हटाना, WebP/AVIF, validation, jobs और tests.

Claude Code से image processing: Sharp, Canvas, WebP/AVIF और upload validation

Image processing product ticket में छोटी लगती है: user photo upload करता है, app thumbnail बनाती है, page fast लगता है. Production में यह security, performance और privacy boundary बन जाती है. आपको upload validate करना, safe filename बनाना, EXIF metadata हटाना, resize और compression करना, WebP/AVIF fallback रखना, heavy काम background job में भेजना और tests लिखना पड़ता है.

Claude Code इस काम में useful है क्योंकि change कई files में होता है. लेकिन prompt vague हुआ तो generated code file.type पर भरोसा कर सकता है, original filename public URL में डाल सकता है, metadata रख सकता है, हर upload को synchronous AVIF में बदल सकता है, और rotated mobile photo test नहीं कर सकता. इसलिए पहले architecture boundary लिखना जरूरी है.

Review करते समय primary docs देखें: Claude Code docs, Sharp resize API, Sharp output API, MDN File API, MDN Canvas toBlob, और OWASP File Upload Cheat Sheet. Browser में heavy processing हो तो internal guide Claude Code Web Worker भी देखें.

Processing boundary पहले तय करें

Format चुनने से पहले तय करें कि कौन सा काम browser, server और background job करेगा.

जगहअच्छा useबचना चाहिए
Browserpreview, हल्का resize, upload traffic कम करनाtrusted validation, bulk AVIF, privacy decision
Server requestMIME, magic bytes, dimensions, EXIF stripping, छोटा thumbnailकई variants, slow AVIF encoding
Background jobproduct image set, CMS regeneration, old images migrationupload response जिसे तुरंत लौटना है

Practical pattern है: browser UX improve करे, server फिर से validate करे, और costly variants job में जाएं. accept="image/*" और file.type user help करते हैं, लेकिन security boundary नहीं हैं.

flowchart LR
  Browser["Browser preview / optional resize"]
  Upload["Upload endpoint"]
  Validate["Magic bytes, size, dimensions"]
  Store["Private raw storage"]
  Job["Background variants"]
  Public["Public WebP/JPEG/AVIF"]
  Browser --> Upload
  Upload --> Validate
  Validate --> Store
  Store --> Job
  Job --> Public

Claude Code को prompt देते समय साफ लिखें: server पर MIME और magic bytes validate करें, public URL में original filename न रखें, rotate() लगाएं, .withMetadata() न बुलाएं, और AVIF optional रहे. इससे plausible लेकिन unsafe code कम आता है.

Product use cases

पहला use case ecommerce या marketplace है. Seller mobile से large photos upload करता है. Product को square thumbnail, card image, detail image और social share image चाहिए. Compression ज्यादा हुआ तो buyer texture, color, label या damage नहीं देख पाएगा. WebP से शुरू करें; AVIF तभी जोड़ें जब real images पर encode time और byte saving माप लें.

दूसरा use case avatar या team profile photo है. यहां square crop, safe URL और privacy important हैं. client-contract-final.png जैसा filename public path में नहीं जाना चाहिए. Browser resize कर चुका हो तब भी server output से EXIF हटना चाहिए.

तीसरा use case blog, help center और course screenshots हैं. Tutorial screenshot में text readable होना चाहिए. अगर button name blur हो गया तो 80 KB बचाने का फायदा नहीं. Document flow के साथ images use हों तो Claude Code PDF generation भी useful है.

चौथा use case SaaS private attachments है: invoices, verification images, support screenshots, admin documents. इन्हें public/uploads में नहीं रखना चाहिए. Private storage, access control, deletion policy और audit logs चाहिए.

Setup

Examples Node.js 20+ मानते हैं. इन्हें Next.js, Express, Hono, Astro API routes या queue worker में use कर सकते हैं.

npm i sharp file-type p-limit
npm i -D tsx typescript @types/node
mkdir -p src public/uploads

CI में image tests चलाने के लिए script जोड़ें.

{
  "scripts": {
    "test:images": "node --import tsx --test src/**/*.test.ts"
  }
}

Upload validation और safe filename

यह module trust boundary है. यह extension पर भरोसा नहीं करता; magic bytes check करता है, Sharp से dimensions पढ़ता है, large files reject करता है, और इस flow में animated या multipage images block करता है.

// src/image-policy.ts
import { randomUUID } from "node:crypto";
import { fileTypeFromBuffer } from "file-type";
import sharp from "sharp";

const MAX_BYTES = 6 * 1024 * 1024;
const MAX_PIXELS = 24_000_000;

const EXTENSION_BY_MIME = {
  "image/jpeg": ".jpg",
  "image/png": ".png",
  "image/webp": ".webp",
  "image/avif": ".avif",
} as const;

export type MimeType = keyof typeof EXTENSION_BY_MIME;

export type ImageUploadInfo = {
  mime: MimeType;
  extension: string;
  width: number;
  height: number;
  bytes: number;
  originalName: string;
};

function isAllowedMime(mime: string): mime is MimeType {
  return mime in EXTENSION_BY_MIME;
}

export async function assertImageUpload(
  buffer: Buffer,
  originalName = "upload",
): Promise<ImageUploadInfo> {
  if (buffer.byteLength === 0) {
    throw new Error("Empty file");
  }

  if (buffer.byteLength > MAX_BYTES) {
    throw new Error("Image must be 6 MB or smaller");
  }

  const detected = await fileTypeFromBuffer(buffer);

  if (!detected || !isAllowedMime(detected.mime)) {
    throw new Error("Unsupported image type");
  }

  const metadata = await sharp(buffer, { failOn: "error" }).metadata();

  if (!metadata.width || !metadata.height) {
    throw new Error("Image dimensions could not be read");
  }

  if (metadata.pages && metadata.pages > 1) {
    throw new Error("Animated images are not allowed here");
  }

  const pixels = metadata.width * metadata.height;

  if (pixels > MAX_PIXELS) {
    throw new Error("Image dimensions are too large");
  }

  return {
    mime: detected.mime,
    extension: EXTENSION_BY_MIME[detected.mime],
    width: metadata.width,
    height: metadata.height,
    bytes: buffer.byteLength,
    originalName,
  };
}

export function safeImageName(mime: MimeType): string {
  return `${randomUUID()}${EXTENSION_BY_MIME[mime]}`;
}

Original name को private display value के रूप में DB में रख सकते हैं, लेकिन public URL में नहीं. Random filename collisions और Unicode filename problems भी कम करता है.

Sharp से resize, compression और EXIF stripping

Node.js server image processing के लिए Sharp practical choice है. Important rule: rotate() से EXIF orientation apply करें, फिर output में metadata न रखें. Public web images के लिए .withMetadata() avoid करना safer default है.

// src/optimize-image.ts
import { mkdir } from "node:fs/promises";
import path from "node:path";
import sharp from "sharp";

type Variant = {
  kind: "thumb" | "card" | "hero";
  width: number;
  height?: number;
};

const VARIANTS: Variant[] = [
  { kind: "thumb", width: 320, height: 320 },
  { kind: "card", width: 640 },
  { kind: "hero", width: 1280 },
];

export type OptimizedImage = {
  src: string;
  width: number;
  height: number;
  bytes: number;
  format: "webp" | "avif";
};

export async function optimizeImage(
  buffer: Buffer,
  outputDir: string,
  baseName: string,
  makeAvif = false,
): Promise<OptimizedImage[]> {
  await mkdir(outputDir, { recursive: true });

  const results: OptimizedImage[] = [];

  for (const variant of VARIANTS) {
    const resized = sharp(buffer)
      .rotate()
      .resize({
        width: variant.width,
        height: variant.height,
        fit: variant.height ? "cover" : "inside",
        withoutEnlargement: true,
      });

    const webpName = `${baseName}-${variant.kind}.webp`;
    const webpInfo = await resized
      .clone()
      .webp({ quality: 78, effort: 4 })
      .toFile(path.join(outputDir, webpName));

    results.push({
      src: `/uploads/${webpName}`,
      width: webpInfo.width,
      height: webpInfo.height,
      bytes: webpInfo.size,
      format: "webp",
    });

    if (makeAvif) {
      const avifName = `${baseName}-${variant.kind}.avif`;
      const avifInfo = await resized
        .clone()
        .avif({ quality: 45, effort: 4 })
        .toFile(path.join(outputDir, avifName));

      results.push({
        src: `/uploads/${avifName}`,
        width: avifInfo.width,
        height: avifInfo.height,
        bytes: avifInfo.size,
        format: "avif",
      });
    }
  }

  return results;
}

AVIF अच्छा format है, लेकिन हर जगह default नहीं होना चाहिए. Encoding slow हो सकती है और WebP से gain हर image में बड़ा नहीं होता. पहले WebP ship करें, फिर AVIF को background comparison से validate करें.

Next.js upload API

यह minimal App Router endpoint है. Private images के लिए public/uploads की जगह object storage और authenticated delivery use करें.

// app/api/images/route.ts
import path from "node:path";
import { NextResponse } from "next/server";
import { assertImageUpload, safeImageName } from "@/src/image-policy";
import { optimizeImage } from "@/src/optimize-image";

export async function POST(request: Request) {
  const form = await request.formData();
  const file = form.get("image");

  if (!(file instanceof File)) {
    return NextResponse.json(
      { error: "image field is required" },
      { status: 400 },
    );
  }

  const buffer = Buffer.from(await file.arrayBuffer());
  const upload = await assertImageUpload(buffer, file.name);
  const storedName = safeImageName(upload.mime);
  const baseName = storedName.replace(/\.[^.]+$/, "");

  const variants = await optimizeImage(
    buffer,
    path.join(process.cwd(), "public", "uploads"),
    baseName,
    false,
  );

  return NextResponse.json({
    original: {
      width: upload.width,
      height: upload.height,
      bytes: upload.bytes,
    },
    variants,
  });
}

यह example public media के लिए है. Invoices, identity images और support attachments को अलग private flow चाहिए.

Browser resize for UX

Browser resize upload size कम करता है और preview fast बनाता है. फिर भी server validation mandatory है.

// src/resize-in-browser.ts
export async function resizeInBrowser(
  file: File,
  maxSide = 1600,
): Promise<File> {
  const bitmap = await createImageBitmap(file);
  const scale = Math.min(1, maxSide / Math.max(bitmap.width, bitmap.height));
  const width = Math.round(bitmap.width * scale);
  const height = Math.round(bitmap.height * scale);

  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;

  const context = canvas.getContext("2d");

  if (!context) {
    throw new Error("Canvas 2D context is not available");
  }

  context.drawImage(bitmap, 0, 0, width, height);
  bitmap.close();

  const blob = await new Promise<Blob>((resolve, reject) => {
    canvas.toBlob(
      (result) => {
        if (result) resolve(result);
        else reject(new Error("Canvas export failed"));
      },
      "image/webp",
      0.82,
    );
  });

  const outputName = file.name.replace(/\.[^.]+$/, ".webp");

  return new File([blob], outputName, {
    type: blob.type || "image/webp",
    lastModified: Date.now(),
  });
}

Canvas re-encode कई metadata हटा देता है, लेकिन privacy के लिए server output पर भरोसा करें. Browser AVIF export भी environment-dependent है.

Background jobs और performance budget

Synchronous upload छोटा रखें: validate, store, और response के लिए जरूरी thumbnail. Detail image, OGP, AVIF और old asset regeneration background job में जाएं.

Starting budget: avatar 320x320 80 KB से कम, card width 640 पर 120 KB से कम, hero width 1280 पर 250 KB से कम. Numbers product के हिसाब से बदलेंगे, लेकिन budget Claude Code को बहुत heavy settings चुनने से रोकता है.

// src/batch-optimize.ts
import { readdir, readFile } from "node:fs/promises";
import path from "node:path";
import pLimit from "p-limit";
import { assertImageUpload, safeImageName } from "./image-policy";
import { optimizeImage } from "./optimize-image";

export async function batchOptimize(inputDir: string, outputDir: string) {
  const files = await readdir(inputDir);
  const limit = pLimit(3);

  const jobs = files.map((file) =>
    limit(async () => {
      const sourcePath = path.join(inputDir, file);
      const buffer = await readFile(sourcePath);
      const upload = await assertImageUpload(buffer, file);
      const baseName = safeImageName(upload.mime).replace(/\.[^.]+$/, "");
      const variants = await optimizeImage(buffer, outputDir, baseName, true);

      return {
        file,
        variants: variants.length,
      };
    }),
  );

  return Promise.allSettled(jobs);
}

Real queue में job ID, source image ID, variant, failure reason, retry count और generated paths store करें. वरना files बचेंगी और DB में उनका मालिक नहीं मिलेगा.

Common failure modes

Common failures clear हैं: file.type पर भरोसा, original filename public URL में डालना, EXIF orientation ignore करना, .withMetadata() से EXIF बचा लेना, request के अंदर AVIF generate करना, या screenshots को इतना compress करना कि text पढ़ा न जाए.

Business failure भी होता है. Course screenshot unreadable हुआ तो paid template CTA की trust घटती है. Product image late load हुई तो buy button दिखता है, पर user product समझ नहीं पाता. Image load, CTA click और purchase path साथ मापना हो तो Claude Code analytics implementation देखें.

Tests

Test में image generate करें ताकि CI self-contained रहे.

// src/image-policy.test.ts
import test from "node:test";
import assert from "node:assert/strict";
import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import sharp from "sharp";
import { assertImageUpload, safeImageName } from "./image-policy";
import { optimizeImage } from "./optimize-image";

test("validates and optimizes a generated image", async () => {
  const input = await sharp({
    create: {
      width: 1200,
      height: 800,
      channels: 3,
      background: "#38bdf8",
    },
  })
    .jpeg()
    .toBuffer();

  const info = await assertImageUpload(input, "masa-profile.jpg");
  assert.equal(info.mime, "image/jpeg");
  assert.equal(info.width, 1200);

  const safeName = safeImageName(info.mime);
  assert.match(safeName, /^[a-f0-9-]+\.jpg$/);

  const outDir = await mkdtemp(path.join(tmpdir(), "images-"));
  const baseName = safeName.replace(/\.[^.]+$/, "");
  const variants = await optimizeImage(input, outDir, baseName, false);

  assert.equal(variants.length, 3);
  assert.ok(variants.every((item) => item.bytes > 0));

  const thumb = await sharp(
    path.join(outDir, `${baseName}-thumb.webp`),
  ).metadata();

  assert.equal(thumb.width, 320);
  assert.equal(thumb.height, 320);
  assert.equal(thumb.exif, undefined);
});

Manual check में mobile width, slow network, broken file, huge file, vertical phone photo, transparent PNG और small-text screenshot शामिल करें. फिर Claude Code से सिर्फ validation, filename, metadata, CPU, fallback और test gaps review करवाएं.

Monetization CTA और verification note

Image processing revenue protect करती है जब user visible content पर भरोसा कर पाता है. Fast product images, readable screenshots, safe avatars और stable OGP next click को support करते हैं. Individual learning के लिए free Claude Code checklist से शुरू करें; reusable prompts और templates के लिए ClaudeCodeLab products देखें; team workflow में upload rules और review gates जोड़ने हों तो training / consultation देखें.

2 June 2026 को Masa ने small Next.js project में यह flow test किया. Best result तब आया जब prompt ने पहले boundary लिखी: browser resize optional है, server validation mandatory है, AVIF optional है, original filename public नहीं होगा. Vague prompt “image upload बनाओ” ने weaker code दिया: file.type validation, public original names, missing orientation handling और synchronous AVIF. Budget और failure cases पहले देने से quality सबसे ज्यादा सुधरी.

#Claude Code #image processing #Sharp #WebP #upload validation
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.