Created: 2026-03-11 Status: Proposal Related:
video-processor-app-architecture.md
Image Processing Consolidation — @repo/image
Problem Statement
Image processing conventions (variant sizes, formats, URL derivation, srcset construction) are scattered across 6+ locations in the monorepo. The same constants and patterns are duplicated independently, creating drift risk and making it hard to evolve the convention.
Current Scatter Map
What's Duplicated
| Concern | Locations | Risk |
|---|---|---|
Variant names (thumbnail, medium, full, xlarge, ultra) | storage-types, process-image, image-preloader, themed-container, imageProcessingService | Add a variant → update 5 files |
| Size defaults (150, 800, 1920, 2560, 3840) | storage-types Zod defaults, process-image inline, imageProcessingService | Size change → update 3 files |
| Variant URL regex | image-preloader, themed-container (near-identical) | Pattern drift |
| srcset builder | image-preloader (buildPreloadSrcset), themed-container (buildBackgroundSrcset) | Divergent srcset logic |
| Variant selection by type (logo→medium, background→ultra) | process-image.ts only (but convention undocumented) | Implicit knowledge |
| Format + quality defaults | storage-types Zod, imageProcessingService | Drift between schema and implementation |
Proposed Solution: @repo/image Package
A new packages/image/ package that becomes the single source of truth for image variant conventions — both producing and consuming sides.
Package Structure
packages/image/
├── src/
│ ├── constants.ts # Variant names, sizes, formats, quality — THE source of truth
│ ├── variants.ts # URL derivation, srcset builders, variant selection
│ ├── types.ts # Re-export refined types (or keep in storage-types and import)
│ ├── server/
│ │ └── process.ts # processUploadedImage (moved from @repo/video/client)
│ └── web/
│ ├── responsive-image.tsx # Responsive <img> with convention-aware srcset
│ └── preload.ts # Preload link helpers (extracted from image-preloader)
├── tsup.config.ts
└── package.json # exports: ./constants, ./variants, ./server, ./web
What Moves Where
| Current Location | Moves To | Notes |
|---|---|---|
storage-types.ts size/format defaults | @repo/image/constants | Types stay in @repo/storage, but defaults imported from @repo/image |
process-image.ts (variant selection, processUploadedImage) | @repo/image/server | Client wrapper stays similar, constants imported |
image-preloader.tsx regex + srcset builder | @repo/image/variants + @repo/image/web | Shared utilities, component stays in app-video-flow but uses shared logic |
themed-container.tsx variant derivation block | @repo/image/variants | ~40 lines of utilities extracted; component imports them |
imageProcessingService.ts size constants | Imports from @repo/image/constants | Service stays in video-processor (Sharp + Cloud Run), just uses shared constants |
What Stays Put
imageProcessingService.tsstays inapps/video-processor— it's a Sharp/Cloud Run concern, not a shared package concern. It just imports constants.storage-types.tskeeps the Zod schemas — they define the API contract. The default values reference@repo/image/constants.- Server actions stay in their apps — they're thin entrypoints. They call
@repo/image/serverinstead of@repo/video/client. progressive-image.tsxstays in design-system — it's a generic component, not image-variant-aware.AnimatedThumbnail.tsxstays in app-library — video thumbnail display is a different concern.
Approach Options
Option A: Full Package Extraction (Recommended)
Create @repo/image with all shared constants, utilities, and the process-image client. Clean separation.
Pros: Single source of truth, clear ownership, easy to find and evolve. Cons: New package to maintain, migration touches several files. Effort: Medium (~15 files touched, mostly imports).
Option B: Consolidate Into @repo/storage
Since storage-types.ts already has the Zod schemas, add the variant utilities there too.
Pros: No new package, smaller change.
Cons: @repo/storage becomes a grab-bag (it's about storage drivers — Redis, Blob, file). Image processing conventions are a different domain. The web/ exports (srcset builders, React components) don't belong in a storage package.
Option C: Consolidate Into @repo/video
process-image.ts already lives in @repo/video/client. Add constants and web utilities there.
Pros: No new package, process-image already there.
Cons: @repo/video is about video processing (Whisper, speech-to-text). Image variant conventions aren't video. The web/ srcset utilities would be awkward in a video package.
Recommendation
Option A — it's the cleanest long-term. The convention surface is specific enough to warrant its own package but broad enough (5 apps consume it) to justify the extraction. It follows the monorepo's existing pattern of domain-focused packages.
Migration Strategy
The refactor should be incremental and non-breaking:
Phase 1: Create package with constants (no consumers yet)
- Create
packages/image/withconstants.tsandvariants.ts - Export variant names, sizes, formats, quality defaults
- Export URL derivation utilities (
deriveVariantUrl,isProcessedImage,buildSrcset) - Build and verify
Phase 2: Migrate consumption side
themed-container.tsx— replace inline utilities with@repo/image/variantsimportsimage-preloader.tsx— replace inline regex/srcset with@repo/image/variantsimports- Server actions — point to
@repo/image/server(or keep via@repo/video/clientif it re-exports)
Phase 3: Migrate production side
imageProcessingService.ts— import size/quality constants from@repo/image/constantsstorage-types.ts— import defaults from@repo/image/constantsfor Zod schemasprocess-image.ts— move to@repo/image/server, re-export from@repo/video/clientfor backward compat
Phase 4: Cleanup
- Remove duplicated constants and utilities from original locations
- Update any remaining imports
- Remove backward-compat re-exports once all consumers are migrated
Benefits
- Add a variant in one place — today adding
xlarge/ultratouched 6 files across 4 packages. With@repo/image, it's one file + the Sharp resize loop. - srcset logic shared — no more near-identical
buildPreloadSrcsetvsbuildBackgroundSrcset. - Variant selection documented — the logo→medium, background→ultra mapping lives in one explicit place.
- Type-safe variant references —
ImageVarianttype exported once, used everywhere. - Easier testing — variant URL derivation can be unit-tested in isolation.