[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Media Pipeline for a SaaS App

Build a Media Pipeline for a SaaS App

Build a complete media pipeline — user uploads, image optimization, responsive delivery, and CDN caching — for a SaaS application using UploadThing, Cloudinary, and imgix.

Development#uploadthing#file-upload#s3#nextjs#images
Works with:claude-codeopenai-codexgemini-clicursor
agent@terminalskills — playground
full playground →

Try this use case on your own files

simulated preview
$
$

real run in an isolated sandbox · files auto-deleted after 7 days · nothing touches your machine

Skills stack · 5 skills

Avg quality 89/100·All SAFE
>

uploadthing

v1.0.0

Handle file uploads in TypeScript apps with UploadThing. Use when a user asks to implement file uploads, handle image uploads in Next.js, add drag and drop file upload, or integrate S3-backed file storage without managing infrastructure.

93/100 quality
2.54× impact
SAFE
View skill
>

cloudinary

v1.0.0

Manage images and videos with Cloudinary. Use when a user asks to optimize images, add image transformations, implement responsive images, upload media, or serve optimized assets from a CDN.

87/100 quality
2.86× impact
SAFE
View skill
>

imgix

v1.0.0

Optimize and transform images with imgix. Use when serving responsive images, implementing image CDN, adding real-time transformations, or optimizing Core Web Vitals with image delivery.

93/100 quality
1.76× impact
SAFE
View skill
>

sharp

v1.0.0

Process and transform images with Sharp for Node.js. Use when a user asks to resize images, convert image formats (WebP, AVIF, PNG, JPEG), compress images, crop or rotate photos, generate thumbnails, add watermarks, optimize images for web, batch process images, create responsive image variants, extract image metadata, or build image processing pipelines. Covers resizing, format conversion, compression, cropping, compositing, and metadata extraction.

80/100 quality
1.45× impact
SAFE
View skill
>

s3-storage

v1.0.0

Manages S3-compatible object storage (AWS S3, MinIO, Cloudflare R2, DigitalOcean Spaces, Backblaze B2, Wasabi, Supabase Storage). Use when the user wants to create buckets, upload/download files, set up lifecycle policies, configure CORS, manage presigned URLs, implement multipart uploads, set up replication, handle versioning, configure access policies, or build file management features on top of S3-compatible APIs. Trigger words: s3, minio, r2, object storage, bucket, presigned url, multipart upload, lifecycle policy, s3 cors, storage backend, file storage, blob storage, spaces, backblaze, wasabi.

93/100 quality
1.96× impact
SAFE
View skill
$

Nadia's project management tool lets users upload avatars, attach files to tasks, and embed screenshots in comments. Currently, images are stored as-is in S3 — a 12MB iPhone photo serves to everyone, killing mobile load times. There's no resizing, no format conversion, no CDN. She builds a proper media pipeline.

Step 1: Upload with Validation

UploadThing handles the upload flow — presigned URLs, multipart for large files, type checking, and auth middleware.

typescript
// app/api/uploadthing/core.ts — Upload routes with validation
import { createUploadthing } from 'uploadthing/next'

const f = createUploadthing()

export const uploadRouter = {
  avatar: f({ image: { maxFileSize: '4MB', maxFileCount: 1 } })
    .middleware(async ({ req }) => {
      const user = await auth(req)
      if (!user) throw new Error('Unauthorized')
      return { userId: user.id }
    })
    .onUploadComplete(async ({ metadata, file }) => {
      // Process through Cloudinary for optimized variants
      const optimized = await cloudinary.uploader.upload(file.url, {
        folder: `avatars/${metadata.userId}`,
        transformation: [
          { width: 256, height: 256, crop: 'fill', gravity: 'face' },
          { quality: 'auto', fetch_format: 'auto' },
        ],
      })
      await db.users.update(metadata.userId, { avatarUrl: optimized.secure_url })
      return { url: optimized.secure_url }
    }),

  taskAttachment: f({
    image: { maxFileSize: '10MB', maxFileCount: 5 },
    pdf: { maxFileSize: '20MB', maxFileCount: 3 },
  })
    .middleware(async ({ req }) => {
      const user = await auth(req)
      const taskId = req.headers.get('x-task-id')
      // Verify user has access to this task
      const hasAccess = await db.tasks.checkAccess(taskId, user.id)
      if (!hasAccess) throw new Error('No access to this task')
      return { userId: user.id, taskId }
    })
    .onUploadComplete(async ({ metadata, file }) => {
      await db.attachments.create({
        taskId: metadata.taskId,
        uploadedBy: metadata.userId,
        name: file.name,
        url: file.url,
        size: file.size,
        type: file.type,
      })
    }),
}

Step 2: Responsive Image Delivery

For task attachments and screenshots, images serve through imgix for on-the-fly resizing. No pre-processing needed.

typescript
// lib/media.ts — Image URL helpers
import ImgixClient from '@imgix/js-core'

const imgix = new ImgixClient({
  domain: 'myapp.imgix.net',
  secureURLToken: process.env.IMGIX_TOKEN,
})

export function getAttachmentUrl(key: string, opts: { width?: number; height?: number } = {}) {
  return imgix.buildURL(key, {
    auto: 'format,compress',
    ...opts.width && { w: opts.width },
    ...opts.height && { h: opts.height },
    fit: 'max',                       // fit within dimensions, no crop
  })
}

export function getAttachmentSrcSet(key: string) {
  return imgix.buildSrcSet(key, {
    auto: 'format,compress',
    fit: 'max',
  })
}

// In component
function AttachmentImage({ attachment }) {
  return (
    <img
      src={getAttachmentUrl(attachment.key, { width: 800 })}
      srcSet={getAttachmentSrcSet(attachment.key)}
      sizes="(max-width: 768px) 100vw, 800px"
      alt={attachment.name}
      loading="lazy"
    />
  )
}

Step 3: Thumbnail Generation

typescript
// For task list view, generate small thumbnails
function TaskAttachmentThumbnail({ attachment }) {
  if (!attachment.type.startsWith('image/')) {
    return <FileIcon type={attachment.type} />
  }

  return (
    <img
      src={getAttachmentUrl(attachment.key, { width: 200, height: 200 })}
      alt={attachment.name}
      className="w-12 h-12 object-cover rounded"
      loading="lazy"
    />
  )
}

// PDF thumbnails via imgix
function PdfThumbnail({ attachment }) {
  const thumbnailUrl = imgix.buildURL(attachment.key, {
    page: 1,           // first page
    fm: 'jpg',
    w: 200,
    h: 280,
  })
  return <img src={thumbnailUrl} alt={`${attachment.name} preview`} />
}

Results

Average image payload drops from 3.2MB to 180KB (94% reduction) — imgix serves WebP/AVIF at the exact dimensions needed. Mobile page load time for task detail goes from 4.2s to 1.1s. The CDN cache hit rate reaches 92% after the first week — subsequent requests for the same image at the same size serve from edge in under 50ms. Avatar uploads process in 2 seconds (upload + Cloudinary face-detect crop + CDN propagation). Zero invalid file uploads — UploadThing's middleware rejects oversized files and wrong types before upload starts. Monthly image delivery cost: $45/month for 500K image requests (vs. $180/month serving raw images from S3 with high bandwidth).