selfhost: route brand assets through /storage/ rewrite

Download 3 Tuxedo brand logos from Supabase Storage to local MinIO and
point the DB at portable /storage/... paths. Add a Next.js rewrite in
next.config.ts that proxies /storage/* to the configured MinIO endpoint.

Why: Next.js's image optimizer refuses to fetch upstream images whose
hostname resolves to a private IP. Local MinIO lives on 127.0.0.1, so
direct URLs (e.g. http://localhost:9000/...) get blocked with
"upstream image resolved to private ip [::1,127.0.0.1]".

The rewrite keeps URLs same-origin in the browser, so the optimizer's
private-IP check is bypassed. For production, set STORAGE_PUBLIC_URL to
the public MinIO endpoint and the same DB values work without change.

Also fix the same pattern in 3 client-rendered components that
hardcoded publicUrl(BUCKETS.BRAND_LOGOS, ...) for fallback assets:
- TuxedoVideoHero (hero video + Olathe Sweet dark logo)
- TimeTrackingFieldClient (field UI logo)
- tuxedo/about (about page logo)

email-service.ts keeps publicUrl() because Resend fetches URLs
server-side from its own network.

Documented the workflow and gotchas in docs/SUPABASE_DUMP_GUIDE.md.
This commit is contained in:
2026-06-05 17:51:48 +00:00
parent cbc8958b55
commit c9b1c49f53
5 changed files with 109 additions and 17 deletions
+5 -5
View File
@@ -6,7 +6,6 @@ import StorefrontHeader from "@/components/storefront/StorefrontHeader";
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
import { supabase } from "@/lib/supabase";
import { getBrandSettingsPublic } from "@/actions/brand-settings";
import { publicUrl, BUCKETS } from "@/lib/storage";
// Lazy load heavy sections
const MissionSection = lazy(() => import("@/components/about/MissionSection"));
@@ -14,10 +13,11 @@ const FamilyTimelineSection = lazy(() => import("@/components/about/FamilyTimeli
const ContactSection = lazy(() => import("@/components/about/ContactSection"));
const CTASection = lazy(() => import("@/components/about/CTASection"));
const OLATHE_SWEET_LOGO_DARK = publicUrl(
BUCKETS.BRAND_LOGOS,
"64294306-5f42-463d-a5e8-2ad6c81a96de/olathe-sweet-logo.png"
);
// Use Next.js rewrite (next.config.ts) so the browser can stream assets
// via same-origin /storage/* and avoid next/image "upstream resolved to
// private ip" failures when the upstream is localhost MinIO.
const OLATHE_SWEET_LOGO_DARK =
"/storage/brand-logos/64294306-5f42-463d-a5e8-2ad6c81a96de/olathe-sweet-logo.png";
export default function TuxedoAboutPage() {
const [logoUrl, setLogoUrl] = useState<string | null>(null);
@@ -5,7 +5,6 @@ import { useEffect, useRef, useState } from "react";
import { motion } from "framer-motion";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { publicUrl, BUCKETS } from "@/lib/storage";
// Register GSAP plugins
if (typeof window !== "undefined") {
@@ -23,12 +22,13 @@ type TuxedoVideoHeroProps = {
onSecondaryClick?: () => void;
};
const VIDEO_URL = publicUrl(BUCKETS.VIDEOS, "tuxedo-hero.mp4");
// Use Next.js rewrite (next.config.ts) so the browser can stream assets
// via same-origin /storage/* and avoid next/image "upstream resolved to
// private ip" failures when the upstream is localhost MinIO.
const VIDEO_URL = `/storage/videos/tuxedo-hero.mp4`;
const OLATHE_SWEET_LOGO_DARK = publicUrl(
BUCKETS.BRAND_LOGOS,
"64294306-5f42-463d-a5e8-2ad6c81a96de/olathe-sweet-logo-dark.png"
);
const OLATHE_SWEET_LOGO_DARK =
"/storage/brand-logos/64294306-5f42-463d-a5e8-2ad6c81a96de/olathe-sweet-logo-dark.png";
// ─────────────────────────────────────────────────────────────────────────────
// CINEMATIC HERO - SCROLL-DRIVEN ANIMATIONS
@@ -4,7 +4,6 @@
import Image from "next/image";
import { useState, useEffect, useRef } from "react";
import LayoutContainer from "@/components/layout/LayoutContainer";
import { publicUrl, BUCKETS } from "@/lib/storage";
import {
verifyTimeTrackingPin,
clockInWorker,
@@ -22,10 +21,10 @@ import {
const BRAND_ID = "64294306-5f42-463d-a5e8-2ad6c81a96de";
const BRAND_NAME = "Tuxedo Corn";
const OLATHE_SWEET_LOGO_DARK = publicUrl(
BUCKETS.BRAND_LOGOS,
`${BRAND_ID}/olathe-sweet-logo.png`
);
// Use Next.js rewrite (next.config.ts) so the browser can stream assets
// via same-origin /storage/* and avoid next/image "upstream resolved to
// private ip" failures when the upstream is localhost MinIO.
const OLATHE_SWEET_LOGO_DARK = `/storage/brand-logos/${BRAND_ID}/olathe-sweet-logo.png`;
// ── Translations ───────────────────────────────────────────────────────────────