Wrote up how I replaced all the ugly S3 URLs on my Laravel blog with clean /storage/media/... and /storage/og-images/... paths.
The setup: Laravel + Octane + Traefik + Cloudflare. Two buckets -- a private one for uploaded media and a public one for auto-generated OG images.
What's in the post:
- MediaUrlBusiness helper class that centralizes URL generation (replaced 6+ blade templates of raw Storage::url() calls)
- ObjectProxyController that streams files directly from S3 using readStream() + response()->stream() -- no memory buffering
- Cache-Control: public, max-age=86400, immutable so Cloudflare caches aggressively
- Route setup in routes/static.php with a middleware tweak that doesn't overwrite the proxy's own cache headers
One gotcha: Storage::download() and streamDownload() buffer the whole file into memory. Switching to readStream() sends it directly from S3 to the client.
danielpetrica.com/how-to-replace-raw-s3-urls-with-a-laravel-image-proxy-and-keep-your-cdn-cache