Resizing & image CDN
Your app can't bundle an image-resize library (libvips/sharp need native code, which the runtime doesn't allow), so onvibe does the resizing for you. There are two ways to get sized, optimized images, and they solve different problems.
On-the-fly — resize when you serve (the CDN)
Point at an upload your app already has and ask for any size/format in the URL. The variant is generated the first time it's requested and cached on images.onvibe.run — repeat requests are served from cache, and these variants don't count against your storage quota (they're a regenerable cache, not stored files).
import { uploadFile, imageUrl, imageSrcset } from "./helpers.ts";
// 1. Store the original once (private upload). Keep the returned key.
const { key } = await uploadFile(await file.arrayBuffer(), file.type, file.name);
await pool.query("INSERT INTO photos (image_key) VALUES ($1)", [key]);
// 2. Serve any size on demand — no need to decide sizes up front.
const thumb = await imageUrl(key, { w: 320, h: 320, fit: "cover" }); // 320×320 webp
const hero = await imageUrl(key, { w: 1200, format: "avif", quality: 70 });
// 3. Responsive images: one call builds the srcset.
const srcset = await imageSrcset(key, [320, 640, 1024], { fit: "cover" });
//
Options (all optional): w, h (pixels; 0/omitted = keep aspect), fit: "cover" "contain" (default cover), format: "webp" "jpeg" "png" "avif" (default webp), quality: 1..100.
The uploadId you pass is the key of an upload of this project — the value uploadFile() (or createDirectUpload) returns. The URL is signed per-project, so only your app can request variants of your uploads.
Use on-the-fly when you don't know the sizes ahead of time, want responsive srcset, or don't want to pay storage for every size.
Preprocess — resize when you upload (stored variants)
When you want a fixed set of sizes materialized as real files (permanent URLs, counted in your quota), generate them at upload time.
import { uploadImage, resizeUpload } from "./helpers.ts";
// Generate named variants from raw bytes; they're public by default.
const { variants } = await uploadImage(await file.arrayBuffer(), {
variants: {
thumb: { w: 200, h: 200, fit: "cover" },
card: { w: 800, format: "webp", quality: 80 },
},
});
// variants.thumb.url / variants.card.url → drop straight into
.
// Or resize an upload you already stored (optionally discard the big original):
await resizeUpload(existingKey, {
variants: { web: { w: 1600, format: "webp" } },
discardOriginal: true,
});
Each variant spec takes the same w/h/fit/format/quality. Variants are public and permanent by default ({ public: false } makes them private signed URLs). Use preprocess when the sizes are known and always needed, or to shrink a large original before you store it.
Which one do I want?
You want… Use
Responsive srcset, arbitrary sizes, no quota cost on-the-fly (imageUrl / imageSrcset)
A fixed set of sizes as permanent files preprocess (uploadImage / resizeUpload)
To shrink a huge original before storing it preprocess with discardOriginal: true
Your own static images (logos, backgrounds) shipped with the app upload_asset → assetUrl(name) (public CDN, see Files & images)
The three image hosts
- assets.onvibe.run — static assets you ship with the app (immutable, cached). Counts to quota.
- storage.onvibe.run — end-user uploads and preprocessed variants. Counts to quota.
- images.onvibe.run — on-the-fly signed variants. Cached, regenerable, no quota.
For the basics of uploading and serving files, see Files & images.