Skip to main content
Every image you upload has an image_url: the whole image, served from a CDN. Image URLs are open, so you make any size, crop or format of an image by writing imgproxy processing options into its URL. There is no API call per rendition, and frameworks’ image components can build their srcset themselves. Assets covers uploading files and managing them.

Image URLs

An asset that is a ready JPEG, PNG, GIF, WebP, AVIF or SVG image has an image_url (it is null for other files):
Write processing options before /plain/, and a format after @:
That is a 1200×630 crop, around the most interesting part of the image, as WebP. Images and SVGs also have a preview_url, a small WebP that fits in 640×640, for thumbnails.

Common options

Options combine in any order: rs:fill:400:400/g:sm/dpr:2/q:80. imgproxy’s processing options list them all.

Frameworks

Image components take a loader that turns a source and a width into a URL. @nohead/sdk/images has one for each, with no dependencies; it leaves other images, the site’s own, as they are:
  • Next.js: in a file named by images.loaderFile in next.config, export { nextImageLoader as default } from "@nohead/sdk/images". Then <Image src={asset.image_url} width={1200} height={630} alt={asset.alt ?? ""} /> gets a srcset of renditions.
  • Astro: in a module named by image.service.entrypoint in astro.config, export { astroImageService as default } from "@nohead/sdk/images".
  • Nuxt Image: in a provider file in nuxt.config’s image.providers, export { nuxtImageProvider as default } from "@nohead/sdk/images" (on Nuxt Image 1, export { getImage } from "@nohead/sdk/images").
In your own code, imageUrl(asset, { width: 800, fit: "cover", format: "webp" }) writes the options into an asset’s URL; Python has nohead.images.image_url and Ruby Nohead::Images.url (SDKs). Without the SDK, a loader only has to add the options before /plain/:

Limits

So that any URL stays cheap to serve (Assets has the upload limits):
  • Renditions are at most 4096 pixels a side, and originals at most 50 megapixels.
  • Options that would return a file as stored are refused: raw, skip_processing and strip_metadata. Renditions never carry a photo’s metadata, such as its location.
  • An SVG’s image_url serves it as SVG with its scripts removed, and other options are ignored; add a format, such as @webp, for a raster image. Its file_url downloads it as uploaded.
  • Each rendition is cached for a year. Requests from one address are rate limited, generously enough for pages full of images.

Video, audio and other files

Every asset also has a file_url once its upload completes, which downloads the file as you uploaded it under its filename, and video and audio have a media_url, which plays it in the browser (it is null for other files). Both are open URLs on a CDN, like image URLs, and never expire:
  • Video and audio play as uploaded, with seeking. MP4, QuickTime, M4A, 3GP, WebM, Matroska, MP3, AAC, FLAC, WAV and Ogg files get a media_url; other types only download. Browsers play MP4 (H.264 with AAC audio), WebM, MP3, AAC and WAV everywhere, so prefer those. Each file must fit your plan’s upload limit.
  • A file uploaded as one of those types must be one: completing an upload whose content doesn’t match its declared type fails, as it does for images.