Use next/image with Supabase Storage images
Updated . Prices checked against the sources below.
next/image needs to know how to resize a remote image. For a Supabase Storage bucket there are three options: let Vercel's optimizer do it, point a custom loader at Supabase's transformations, or point a loader at an image CDN. Each changes where images are processed and how you're billed.
Option 1: Vercel's optimizer with remotePatterns
Allow your bucket's public URL and keep the default loader. Next.js requests /_next/image, and on Vercel each cache miss or stale revalidation is a billed transformation. AVIF and WebP are cached separately.
Default loader
// next.config.ts
const nextConfig = {
images: {
remotePatterns: [new URL("https://xyz.supabase.co/storage/v1/object/public/**")],
minimumCacheTTL: 2678400, // cache for 31 days instead of the 1-hour default
},
}
export default nextConfig
// In a component
<Image src="https://xyz.supabase.co/storage/v1/object/public/avatars/cory.png" width={128} height={128} alt="Avatar" />Option 2: a loader for Supabase's transformations
Supabase's docs include a custom loader that sends next/image requests to the render endpoint. Images are resized by Supabase and delivered from Supabase's CDN. It needs a Supabase Pro plan or higher, and each origin image beyond the included 100 is billed.
Supabase loader
// supabase-image-loader.ts
"use client"
const projectId = "xyz" // your Supabase project ID
export default function supabaseLoader({ src, width, quality }: { src: string; width: number; quality?: number }) {
return `https://${projectId}.supabase.co/storage/v1/render/image/public/${src}?width=${width}&quality=${quality || 75}`
}
// next.config.ts
// images: { loader: "custom", loaderFile: "./supabase-image-loader.ts" }
// In a component: src is "bucket/path"
<Image src="avatars/cory.png" width={128} height={128} alt="Avatar" />Option 3: a loader for an image CDN
Point the loader at ProdKit and images are resized by Cloudflare Images and served from Cloudflare's network, wherever the app is deployed. It works on any Supabase plan, and the same URLs work in React Native and Swift.
ProdKit loader
// prodkit-loader.ts
"use client"
export default function prodkitLoader({ src, width, quality }: { src: string; width: number; quality?: number }) {
// src is the object path inside your bucket, e.g. "avatars/cory.png"
const url = new URL(`https://img.prodkit.cloud/acme/${src.replace(/^\//, "")}`)
url.searchParams.set("width", String(width))
if (quality) url.searchParams.set("quality", String(quality))
return url.toString()
}
// next.config.ts
// images: { loader: "custom", loaderFile: "./prodkit-loader.ts" }Comparing the three
- Vercel's optimizer: nothing extra to set up on Vercel. Billed per cache miss; only works for apps deployed on Vercel.
- Supabase loader: stays inside Supabase. Pro plan required, $5 per 1,000 origin images after 100, outputs up to 2,500px.
- ProdKit loader: works anywhere and on Supabase Free. Billed per origin image, from $0 for 1,000 a month.
Related
Try it on your bucket
The free plan covers 1,000 origin images a month and never bills. Connect a public bucket and replace one URL.
Sources
Checked on September 28, 2026. Prices change, so confirm against the source before deciding.