# Images

Optional image optimization with `rebaskImage()` and `<Image>`.

## Image plugin setup

Install `sharp`, which the built-in handler uses:

::package-manager

```bash [pnpm]
pnpm add sharp
```

```bash [npm]
npm install sharp
```

```bash [bun]
bun add sharp
```

```bash [yarn]
yarn add sharp
```

::

Add `rebaskImage()` to your Vite plugins:

```ts
// vite.config.ts
import { rebaskImage } from "@rebask/core/vite/image"

plugins: [rsc(), react(), rebask(), rebaskImage()]
```

Create `rebask.image.ts` at the project root:

```ts
// rebask.image.ts
import { handleImage } from "@rebask/core/image"
import sharp from "sharp"

export default function handler(request: Request) {
  return handleImage(request, sharp)
}
```

Local images are read from `public/`. Remote URLs must match a [`remotePatterns`](#remote-patterns) entry, and `localPatterns` can restrict local paths.

Adapters can provide their own image optimization. For example, [`vercelAdapter()`](./production/vercel.md#images) routes `/_rebask/image` to Vercel's optimizer when `rebaskImage()` is configured. In that setup, you don't need `rebask.image.ts` or Sharp.

## `<Image>`

Use `width` and `height` for fixed-size images:

```tsx
import { Image } from "@rebask/core/components/image"

<Image src="/hero.jpg" alt="Storefront" width={1200} height={600} preload />
<Image src="/product.jpg" alt="Product" width={800} height={800} />
```

Add `sizes` for responsive images. It tells the browser how wide the image will render:

```tsx
<Image
  src="/hero.jpg"
  alt="Storefront"
  width={1200}
  height={600}
  sizes="(min-width: 1200px) 1200px, 100vw"
/>
```

## Static imports

Import an image and pass it as `src`. Static imports are optimized through the handler like other
local images, and their dimensions are read at build time, so `width` and `height` are not needed:

```tsx
import hero from "./hero.jpg"

<Image src={hero} alt="Storefront" placeholder="blur" />
```

`placeholder="blur"` uses `sharp` to generate the blur at build time. Without `sharp`, pass
`blurDataURL` to `<Image>`.

Static imports are not subject to `localPatterns`.

## Fill

`fill` makes the image cover a positioned parent. Give the parent dimensions:

```tsx
<div style={{ position: "relative", width: "100%", aspectRatio: "2 / 1" }}>
  <Image
    src="/hero.jpg"
    alt="Storefront"
    fill
    sizes="(min-width: 1200px) 1200px, 100vw"
  />
</div>
```

## `<Image>` props

| Prop               |                                              |
| ------------------ | -------------------------------------------- |
| `src`              | Path or URL                                  |
| `alt`              | Required                                     |
| `width` / `height` | Required unless `fill`                       |
| `sizes`            | Rendered width, used for responsive images   |
| `fill`             | Fill the positioned parent                   |
| `quality`          | 1–100, snapped to the configured `qualities` |
| `preload`          | Preload the image                            |
| `loading`          | `"lazy"` \| `"eager"`                        |
| `placeholder`      | `"blur"` \| `"empty"` \| data URL            |
| `blurDataURL`      | Required with `placeholder="blur"`           |
| `unoptimized`      | Skip optimization                            |
| `loader`           | Custom loader                                |

SVG, `data:`, and `blob:` URLs are not optimized. The handler rejects SVG unless
`dangerouslyAllowSVG` is enabled. Other `<img>` attributes, such as `className`, `style`,
`fetchPriority`, and `onLoad`, are passed through.

## `rebaskImage()` options

These are the defaults:

```ts
rebaskImage({
  deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  imageSizes: [32, 48, 64, 96, 128, 256, 384],
  path: "/_rebask/image",
  qualities: [75],
  formats: ["image/webp"],
  contentSecurityPolicy: "script-src 'none'; frame-src 'none'; sandbox;",
  contentDispositionType: "attachment",
  minimumCacheTTL: 14400,
  unoptimized: false,
  localPatterns: undefined,
  remotePatterns: [],
  dangerouslyAllowSVG: false,
  dangerouslyAllowLocalIP: false,
  maximumRedirects: 3,
  maximumResponseBody: 50000000, // 50 MB
  maximumCacheSize: 50000000, // 50 MB
  handler: "rebask.image.ts",
})
```

- `deviceSizes` sets the widths used for responsive images.
- `imageSizes` sets the widths used for fixed-size images.
- `path` sets the endpoint and the URLs generated by `<Image>`.
- `qualities` sets the allowed quality values. Other values snap to the closest one.
- `formats` sets the formats offered to clients that advertise support.
- `contentSecurityPolicy` sets the CSP header on image responses.
- `contentDispositionType` sets `inline` or `attachment` on image responses.
- `minimumCacheTTL` sets the minimum cache lifetime for public images. A longer upstream `Cache-Control` wins.
- `unoptimized` disables optimization for all images.
- `localPatterns` limits which files under `public/` can be optimized. Leave it undefined to allow all local images.
- `remotePatterns` lists the remote URLs the server may fetch. No remote images are allowed by default.
- `dangerouslyAllowSVG` serves SVG images instead of rejecting them. Only enable it for trusted sources.
- `dangerouslyAllowLocalIP` allows remote images from private or loopback addresses. Leave it false to block SSRF.
- `maximumRedirects` sets how many redirects a remote image may follow.
- `maximumResponseBody` caps the remote image size in bytes.
- `maximumCacheSize` caps the in-memory image cache in bytes. Caching is disabled in development.
- `handler` sets the handler file. Rebask also detects `rebask.image.js`.

## Image caching

Optimized images are cached in memory in production. The handler follows the upstream `Cache-Control` header:

- `no-store` and `private` responses bypass the shared cache. `private` responses stay private to the browser.
- `no-cache` and `max-age=0` responses bypass the shared cache and are returned with `max-age=0, must-revalidate`.
- `s-maxage` and `max-age` set the cache lifetime, with `minimumCacheTTL` as the floor for public images with a positive max age.

## Remote patterns

Remote images need an entry in `remotePatterns`:

```ts
rebaskImage({
  remotePatterns: [
    { hostname: "images.example.com", pathname: "/products/**" },
    { protocol: "https", hostname: "*.cdn.example.com" },
  ],
})
```

Each pattern can set `protocol`, `hostname`, `port`, `pathname`, and `search`. `hostname` is
required. Patterns match the whole value.

- `*` matches a single segment, such as one subdomain or one path segment.
- `**` matches any number of segments.
- `search` matches the query string exactly, including `?`. An empty string matches no query string.
- When `protocol`, `port`, `pathname`, or `search` is omitted, any value matches.

`localPatterns` uses the same `pathname` and `search` rules:

```ts
rebaskImage({
  localPatterns: [
    { pathname: "/images/**" },
    { pathname: "/products/*.jpg", search: "" },
  ],
})
```
