# F-055 — Implementer Report ## Feature Image thumbnails: server-rendered cache + CSS max-size ## Approach Originally planned on-demand resize via route handler (`?w=40` / `?w=200`). Discovered that Next.js 16 prioritizes static files in `public/` over the `app/uploads/[filename]/route.ts` handler, making the query-param approach unworkable. Switched to pre-generating thumbnails to disk under `public/uploads//` and using static URLs (`/uploads/40/foo.jpg`, `/uploads/200/foo.jpg`) which Next.js serves as plain immutable assets. ## Changes Made ### 1. Thumbnail pre-generation script **New files:** - `project/scripts/generate-thumbnails.sh` — wrapper that locates sharp - `project/scripts/generate-thumbnails.mjs` — node script using sharp The script reads source images from `apps/admin/public/uploads/` and generates 40px and 200px thumbnails in each app's `public/uploads/40/` and `public/uploads/200/` directories. Existing thumbnails are skipped, so re-runs are idempotent and fast. ### 2. Sync uploads mirror thumbnails too **File:** `project/scripts/monolith.sh` `sync_uploads()` now mirrors `find -maxdepth 2` (covers `/public/uploads//`) to all three apps and calls `generate-thumbnails.sh --quiet` after syncing. ### 3. CSS max-height on product detail pages **Files:** - `project/storefront/src/app/productos/[slug]/page.tsx` → `max-h-[600px]` - `project/frontend/src/app/products/[slug]/page.tsx` → `max-h-[500px]` to image container ### 4. UI uses static thumbnail URLs **Files:** - `project/apps/admin/src/features/products/components/sections/ImagesSection.tsx` → `img.url.replace('/uploads/', '/uploads/200/')` - `project/apps/admin/src/app/(dashboard)/products/page.tsx` → `p.imageUrl.replace('/uploads/', '/uploads/40/')` ### 5. Old dynamic route handlers removed **Removed:** `app/uploads/[filename]/route.ts` in admin, frontend, and storefront. Reason: Next.js 16 prioritizes static files in `public/` over route handlers, so the `?w=` approach silently served the original full-size image regardless of query params. Static URLs `/uploads/40/...` avoid the collision. **Tradeoff:** Newly uploaded images (after `next start`) won't be served immediately because Next.js caches the `public/` listing at build/start. This was already the case before this change; thumbnails don't make it worse. ### 6. Dependencies `sharp` was installed in: - `project/apps/admin/package.json` - `project/frontend/package.json` - `project/storefront/package.json` ## Verification ```bash # Generate thumbnails for existing images ./project/scripts/generate-thumbnails.sh # Original (admin) curl -sI "http://localhost:3004/uploads/fa7a31db...jpg" | grep -i length # Content-Length: 129711 (600x800) # 40px thumbnail curl -sI "http://localhost:3004/uploads/40/fa7a31db...jpg" | grep -i length # Content-Length: 1155 (40x53) # 200px thumbnail curl -sI "http://localhost:3004/uploads/200/fa7a31db...jpg" | grep -i length # Content-Length: 12164 (200x267) # Same in frontend (3003) and storefront (3005): identical sizes ``` ## Files Changed - `project/scripts/monolith.sh` — sync uploads mirrors thumbnails - `project/scripts/generate-thumbnails.sh` — new - `project/scripts/generate-thumbnails.mjs` — new - `project/storefront/src/app/productos/[slug]/page.tsx` — max-h-[600px] - `project/frontend/src/app/products/[slug]/page.tsx` — max-h-[500px] - `project/apps/admin/src/app/(dashboard)/products/page.tsx` — /uploads/40/ - `project/apps/admin/src/features/products/components/sections/ImagesSection.tsx` — /uploads/200/ - `project/frontend/src/app/uploads/[filename]/route.ts` — DELETED - `project/storefront/src/app/uploads/[filename]/route.ts` — DELETED - `project/apps/admin/src/app/uploads/[filename]/route.ts` — DELETED ## Note on Subsequent Uploads After uploading a new image through admin: 1. Image is mirrored to frontend/storefront by `api/upload/route.ts` 2. Run `./project/scripts/generate-thumbnails.sh` to create the thumbnails 3. Restart the affected app if you want the new image served immediately (otherwise it appears after the next build/start)