# 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).

```ts
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" });
// <img srcset="{srcset}" sizes="(max-width:600px) 100vw, 640px" src="{thumb}">
```

**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.

```ts
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 <img src>.

// 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](/docs/files-and-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](/docs/files-and-images).
