3.9 KiB
F-056 — Implementer evidence
Scope delivered
Two distinct defects were addressed in this branch:
- Uploads 404 / 502 on freshly uploaded files. Next.js caches the
public/directory listing at server start, so any file written topublic/uploads/afternext startreturns 404 from the static handler andnext/imageanswers 502 because its upstream fetch fails. - Product detail image overflows its container because the wrapper had
no positioning context for
next/image fill.
Changes
1. Dynamic uploads route (per app)
Three new route handlers were added — one per Next.js frontend
(apps/admin, frontend, storefront). Each reads uploads from the local
public/uploads/ and the canonical apps/admin/public/uploads/ directory on
every request, so a freshly uploaded file resolves without restart or rebuild:
project/apps/admin/src/app/uploads/[...path]/route.tsproject/frontend/src/app/uploads/[...path]/route.tsproject/storefront/src/app/uploads/[...path]/route.ts
Each handler:
- Sets
dynamic = 'force-dynamic'so Next.js does not cache the route at build. - Validates the path against
SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/to block traversal. - Maps a known extension to a content type and serves the file with
Cache-Control: public, max-age=31536000, immutable. - Generates the
40and200thumbnails on demand viasharpand caches them on disk, so/uploads/40/<file>and/uploads/200/<file>resolve without requiringgenerate-thumbnails.sh.
2. Inline thumbnail generation on upload
project/apps/admin/src/app/api/upload/route.ts now calls generateThumbnails
immediately after mirrorToPeers, producing the 40 and 200 px variants in every
mirror location. Failures are non-fatal: the dynamic handler above regenerates
the missing thumbnail on the next request.
3. Product detail image container
project/frontend/src/app/products/[slug]/page.tsx adds the relative class
to the image wrapper so next/image fill is positioned correctly inside the
max-h-[500px] overflow-hidden container. The image stays inside its box on
all viewports.
Acceptance traceability
| Acceptance criterion | How it is met |
|---|---|
| New upload file loads 200 without server restart | Dynamic /uploads/[...path]/route.ts reads from disk per request. Verified with curl on a freshly uploaded file. |
| Thumbs 40 and 200 exist right after upload | generateThumbnails runs inline in /api/upload after mirrorToPeers. |
next/image no more 502 for fresh uploads |
Static 404 (which became 502 upstream) is replaced by the dynamic handler that returns 200 from disk. |
| Product detail image stays inside its box | relative class added so next/image fill is contained by aspect-square max-h-[500px] overflow-hidden. |
Commands run during build
pnpm --filter ./project/apps/admin exec tsc --noEmit— typecheck pass.pnpm --filter ./project/frontend exec tsc --noEmit— typecheck pass.pnpm --filter ./project/storefront exec tsc --noEmit— typecheck pass../scripts/verify.sh— exit code 0.- Manual curl:
/uploads/40/8223b962-642c-4c95-bc2e-37c2d2fad4ac.jpg→ HTTP 200, 568 bytes.
Files touched
project/apps/admin/src/app/api/upload/route.ts (modified)
project/apps/admin/src/app/uploads/[...path]/route.ts (new)
project/frontend/src/app/uploads/[...path]/route.ts (new)
project/frontend/src/app/products/[slug]/page.tsx (modified)
project/storefront/src/app/uploads/[...path]/route.ts (new)
Artifacts produced
project/apps/admin/public/uploads/40/<file>.jpg— generated inline by upload route.project/frontend/public/uploads/40/<file>.jpg— generated inline by upload route.project/storefront/public/uploads/40/<file>.jpg— generated inline by upload route.
Status
Build completed. Ready for reviewer/security/qa gates.