fix: photo thumbnails always used localhost:9000 in production
getPublicUrl() runs in the browser (called from client components rendering recipe thumbnails), but read the plain STORAGE_PUBLIC_URL env var — never inlined into the client bundle, so every browser fell back to the hardcoded localhost:9000 default regardless of the real deployed storage domain, tripping CSP img-src and mixed-content blocks in production. Added a NEXT_PUBLIC_STORAGE_PUBLIC_URL build arg (Dockerfile, compose.prod.yml) wired from the same STORAGE_PUBLIC_URL value, and getPublicUrl() now reads that. Verified locally: building with a fake public storage domain set shows it correctly inlined into the client JS chunk (previously only the localhost fallback ever appeared there).
This commit is contained in:
@@ -44,6 +44,11 @@ ENV NEXT_PUBLIC_AUTHENTIK_ENABLED=$NEXT_PUBLIC_AUTHENTIK_ENABLED
|
|||||||
# also needs to be a build arg, not just a runtime env var (unlike STORAGE_ENDPOINT).
|
# also needs to be a build arg, not just a runtime env var (unlike STORAGE_ENDPOINT).
|
||||||
ARG STORAGE_PUBLIC_URL
|
ARG STORAGE_PUBLIC_URL
|
||||||
ENV STORAGE_PUBLIC_URL=$STORAGE_PUBLIC_URL
|
ENV STORAGE_PUBLIC_URL=$STORAGE_PUBLIC_URL
|
||||||
|
# lib/storage.ts's getPublicUrl() is called from client components to render photo
|
||||||
|
# <img> tags — a plain (non-NEXT_PUBLIC_) env var is never inlined into the browser
|
||||||
|
# bundle, so it must be duplicated under a NEXT_PUBLIC_ name to reach the client.
|
||||||
|
ARG NEXT_PUBLIC_STORAGE_PUBLIC_URL
|
||||||
|
ENV NEXT_PUBLIC_STORAGE_PUBLIC_URL=$NEXT_PUBLIC_STORAGE_PUBLIC_URL
|
||||||
RUN pnpm --filter web build
|
RUN pnpm --filter web build
|
||||||
|
|
||||||
# ---- runtime ----
|
# ---- runtime ----
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|||||||
const bucket = process.env["STORAGE_BUCKET"] || "epicure-uploads";
|
const bucket = process.env["STORAGE_BUCKET"] || "epicure-uploads";
|
||||||
const endpoint = process.env["STORAGE_ENDPOINT"] || "http://localhost:9000";
|
const endpoint = process.env["STORAGE_ENDPOINT"] || "http://localhost:9000";
|
||||||
const publicUrl = process.env["STORAGE_PUBLIC_URL"] || "http://localhost:9000";
|
const publicUrl = process.env["STORAGE_PUBLIC_URL"] || "http://localhost:9000";
|
||||||
|
// getPublicUrl() below runs in the browser too — non-NEXT_PUBLIC_ vars are never
|
||||||
|
// inlined into the client bundle, so it needs its own NEXT_PUBLIC_ copy of the value.
|
||||||
|
const clientPublicUrl = process.env.NEXT_PUBLIC_STORAGE_PUBLIC_URL || "http://localhost:9000";
|
||||||
|
|
||||||
const credentials = {
|
const credentials = {
|
||||||
accessKeyId: process.env["STORAGE_ACCESS_KEY"] ?? "minioadmin",
|
accessKeyId: process.env["STORAGE_ACCESS_KEY"] ?? "minioadmin",
|
||||||
@@ -39,5 +42,5 @@ export async function deleteObject(key: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getPublicUrl(key: string): string {
|
export function getPublicUrl(key: string): string {
|
||||||
return `${publicUrl}/${bucket}/${key}`;
|
return `${clientPublicUrl}/${bucket}/${key}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ services:
|
|||||||
NEXT_PUBLIC_DISCORD_ENABLED: ${NEXT_PUBLIC_DISCORD_ENABLED}
|
NEXT_PUBLIC_DISCORD_ENABLED: ${NEXT_PUBLIC_DISCORD_ENABLED}
|
||||||
NEXT_PUBLIC_AUTHENTIK_ENABLED: ${NEXT_PUBLIC_AUTHENTIK_ENABLED}
|
NEXT_PUBLIC_AUTHENTIK_ENABLED: ${NEXT_PUBLIC_AUTHENTIK_ENABLED}
|
||||||
STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL}
|
STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL}
|
||||||
|
NEXT_PUBLIC_STORAGE_PUBLIC_URL: ${STORAGE_PUBLIC_URL}
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
||||||
|
|||||||
Reference in New Issue
Block a user