Profile photos were being rendered with next/image but flagged unoptimized, and the
public card page used a raw <img> tag with an eslint-disable comment claiming the remote
host was "unknown per-card, not worth allowlisting." Both bypassed Next.js's image
optimization.
The "unknown remote host" reasoning was outdated: Supabase Storage is served from the same
configured origin as the Supabase API (NEXT_PUBLIC_SUPABASE_URL) — a single known origin,
not arbitrary per-card hosts. This change allowlists that origin explicitly and routes both
photo render sites through next/image.
- Added a
supabaseStoragePattern()helper that parsesNEXT_PUBLIC_SUPABASE_URLand derives aremotePatternsentry (protocol/hostname/port) for the exact configured origin. - The derived origin is added to
images.remotePatterns, enabling optimization for the configured Storage host (local dev + hosted + custom domains). - Retained the existing fallbacks:
{ protocol: "http", hostname: "127.0.0.1", port: "54321" }— localsupabase startdefault.{ protocol: "https", hostname: "*.supabase.co" }— hosted fallback / older deployments.
- Removed the
unoptimizedprop from the<Image>so the profile editor photo is optimized.
- Replaced the raw
<img>(and its@next/next/no-img-elementeslint-disable comment) withnext/image, keeping the existing fixed80×80display dimensions (h-20 w-20). - Added the
next/imageimport.
- Hosted Supabase Storage lives under
*.supabase.co→ already covered (and now also by the exact derived origin). - Local dev Storage is served from
http://127.0.0.1:54321(the same origin as the API) → already allowlisted. NEXT_PUBLIC_SUPABASE_URLisNEXT_PUBLIC_-prefixed and safe to read at config load time.
npm run lint— passes.npm run typecheck(tsc --noEmit) — passes.npx next build— succeeds (with the full env set; see note below).
Not captured numerically — no live Storage bucket was available to produce a real before/after. Expected impact on the public card page (low-bandwidth-critical audience):
- The photo (often the single largest asset, uploads up to 5 MB) is now resized to the 80×80
display size and transcoded to
webp/avifby the optimizer instead of being shipped byte-for-byte. - To document the exact KB reduction in the PR: compare the Network payload of
/card/[id]for a representative card before and after this change.
next buildinitially failed only becauselib/env.ts'sserverEnvzod schema requiresSOROBAN_RPC_URL(and others). This is a pre-existing environment requirement, unrelated to these edits; the build succeeds once the full env is provided.