Next.js image loader for Supabase Storage

The @prodkit-cloud/next package wraps next/image with a ProdKit loader that works in App Router server and client components.

SDK coming soon

@prodkit-cloud/next isn't published yet, so the guides below skip the install step. Until it is, build image URLs from your endpoint with URL parameters. They work in any Next.js app with no package:

URL API

// image-loader.ts
"use client"

export default function prodkitLoader({ src, width, quality }: { src: string; width: number; quality?: number }) {
  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: "./image-loader.ts" }

// In a component: src is the path in your bucket
// <Image src="avatars/cory.png" width={128} height={128} alt="Avatar" />

Use the App Router component

ProdKitImage is server-component safe and accepts the sizing, priority, className, and sizes props you already pass to next/image.

Example

import { ProdKitImage } from "@prodkit-cloud/next"

export function Cover() {
  return (
    <ProdKitImage
      project="acme"
      src="covers/launch.jpg"
      width={1280}
      height={720}
      quality={82}
      sizes="(max-width: 768px) 100vw, 1280px"
      alt="Launch cover"
    />
  )
}

Crop to faces and remove backgrounds

Crop and effect props go straight to Cloudflare Images. gravity="face" keeps faces in frame, and removeBackground (beta) returns a transparent cutout.

Example

<ProdKitImage
  project="acme"
  src={user.avatarPath}
  width={96}
  height={96}
  gravity="face"
  placeholder="blur"
  alt={user.name}
/>

Use the loader directly

For existing next/image components, create a configured loader and pass it to Image. Next still supplies the responsive width and quality values. A function prop can only be passed from a client component, so the file starts with "use client".

Example

"use client"

import Image from "next/image"
import { createProdKitImageLoader } from "@prodkit-cloud/next"

const prodkitLoader = createProdKitImageLoader({ project: "acme" })

export function Avatar() {
  return (
    <Image
      loader={prodkitLoader}
      src="avatars/cory.png"
      width={128}
      height={128}
      alt="Avatar"
    />
  )
}

Set the loader for every image

To send every next/image in the app through ProdKit, including those in server components, export the loader from a loaderFile. next/image is a client component and runs that file, so it starts with "use client" too.

Example

// image-loader.ts
"use client"

import { createProdKitImageLoader } from "@prodkit-cloud/next"

export default createProdKitImageLoader({ project: "acme" })

// next.config.ts
const nextConfig = {
  images: { loader: "custom", loaderFile: "./image-loader.ts" },
}

export default nextConfig

ProdKitImage props

ProdKitImage accepts project, imageBaseUrl, transform props, and the standard next/image display props. Use createProdKitImageLoader when you want to keep existing Image components.

NameDescriptionValuesDefault
srcImage path within your project, or a full Supabase Storage URL to rewrite.stringRequired
projectProject slug from the dashboard. Can be passed through ProdKitProvider.stringRequired
imageBaseUrlCustom image service base URL for self-hosted or preview environments.absolute URLhttps://img.prodkit.cloud
widthRendered width in CSS pixels. Also caps the generated srcset variants.1-4096Required
heightRendered height in CSS pixels.1-4096Auto from aspect ratio
qualityCompression quality.1-10082
fitHow the image fills the requested dimensions.scale-down, contain, cover, crop, padscale-down
formatOutput format. auto negotiates AVIF or WebP per browser.auto, avif, webp, jpeg, pngauto
gravityWhere cover and crop fits keep the image: face (detected faces), auto (the most interesting region), a side, or a focal point XxY from 0 to 1. Sets fit=cover unless you choose a fit."auto", "face", "top", "bottom", "left", "right", { x, y }Center
zoomWith gravity=face, how tightly to crop around faces, from 0 (loose) to 1 (tight).0-1None
blurBlur radius, rounded to a standard level. With a small width, makes a lightweight placeholder.1-250None
sharpenSharpening strength. 1 suits most downscaled images.0-10None
removeBackgroundCuts out the subject with a transparent background (beta).booleanfalse
placeholderblur shows a tiny blurred version of the image until it loads, generated by ProdKit (no blurDataURL needed). It counts as one of the image's sizes.blur, emptyempty