YouBothAgent▾
You — Business rules and flows you own. Read these yourself.
Both — Know the idea; your agent follows the details.
Agent — Conventions and references your agent follows. Look up as needed.
General▾
Interface▾
Observability▾
Performance▾
Native▾
Development▾

Image Optimization

Someone uploads a 4MB phone photo and it renders as a 96px avatar. The layout looks right, yet every visitor downloads four megabytes for one thumbnail.
Image from akanjs/ui solves this in three steps:
  1. The component writes an optimizer URL for each candidate, such as /_akan/image?url=…&w=640&q=75.
  2. The server resizes and re-encodes the original once, then keeps the result on disk.
  3. The browser picks the candidate from srcSet that fits the screen and downloads only that one.
The rest of this page is about not defeating that cache.
What you do
  • Render with Image. Use it for every image the UI shows.
  • Allow remote hosts. Remote images stay blocked until you list them in images.remotePatterns of akan.config.ts.
  • Keep sizes few. Reuse a handful of sizes so many elements share one cached file.
Words used on this page
optimizer
The /_akan/image endpoint that resizes, re-encodes and caches an image.
srcSet
The candidate URLs on an <img>. The browser downloads only the one that fits.
sizes
How wide the image renders per viewport, e.g. (min-width: 768px) 50vw, 100vw.
1x2x
Two candidates for one fixed width: a normal screen and a high-density one.
640w
A candidate labelled with its pixel width. The browser picks one through sizes.
TTL
How long a downloaded remote image is reused before it is fetched again.

Use Image

Pass a file (a File relation or any { url, imageSize }) or a plain src, plus the width it renders at:
apps/myapp/lib/article/Article.Unit.tsx
  • Always pass the rendered width. Without width it falls back to file.imageSize, the original's width, so a 4000px photo is fetched at 3840px.
  • priority is for the first screen. That image loads eagerly at high priority and is preloaded during SSR. Every other image loads lazily.
  • A custom quality must be allowed. Add the value to qualities in the config, or the optimizer answers 400.
Which srcSet you get
You passsrcSetGood for
width onlyA 1x/2x pair, each rounded up to an allowed widthA fixed-size box: avatar, thumbnail
sizes, with or without widthAll 15 allowed widths as w candidates; width is ignoredA fluid box that follows the viewport
src with no widthThe 8 deviceSizes as w candidatesA full-width image
a data: or blob: URL, an .svg pathNone; the original src is usedInline previews and icons
unoptimizedNone; the original src is usedAn image another service already optimized
  • sizes wins over width. Once it is present, the width no longer shapes the URLs, so give it only to a fluid element.
  • unoptimized is not for SVGs. data:, blob: and .svg sources skip the optimizer on their own. The flag is for images optimized elsewhere.
Props
fileFile | { url, imageSize } | null
The image to show. Its imageSize fills in width and height when you omit them.
srcstring
A direct URL that wins over file.url. With neither, an empty bg-muted box renders and nothing is requested.
widthnumberdefault file.imageSize[0]
The rendered width in CSS pixels. It picks the 1x/2x candidates.
heightnumberdefault file.imageSize[1]
The rendered height in CSS pixels. It reserves space, so the layout does not jump.
sizesstring
Switches to the full w srcSet for a fluid element.
qualitynumberdefault 75
Encoding quality. Any value other than 75 must be listed in qualities.
prioritybooleandefault false
Loads eagerly at high priority and preloads during SSR. preload does the same.
unoptimizedbooleandefault false
Skips the optimizer and renders the original src.
altstringdefault "image"
Alt text. Always pass a real one; the default tells a screen reader nothing.

Config

Every optimizer setting lives under the images key of akan.config.ts. An array you write replaces its default outright: setting deviceSizes drops all eight defaults.
Sizes, formats and quality
deviceSizesnumber[]default [640, 750, 828, 1080, 1200, 1920, 2048, 3840]
Widths for viewport-wide images. Together with imageSizes, the only w values accepted.
imageSizesnumber[]default [32, 48, 64, 96, 128, 256, 384]
Widths for fixed-size elements, joined with deviceSizes.
formats("image/avif" | "image/webp")[]default ["image/webp"]
Output formats in preference order. The first one the request's Accept header allows wins.
qualitiesnumber[]default [75]
Allowed q values. Anything else is a 400, even a valid integer from 1 to 100.
Allowed sources
remotePatterns{ protocol?, hostname?, port?, pathname?, search? }[]default []
Allow-list for absolute URLs. Empty means no remote image is accepted at all.
localPatterns{ pathname?, search? }[]default [{ pathname: "/**" }]
Allow-list for root-relative URLs. The default admits all of public/.
dangerouslyAllowSVGbooleandefault false
Lets an SVG through the optimizer untouched. While off, an SVG input is a 400.
Fetching remote images
minimumCacheTTLnumberdefault 14400
Seconds a remote image is reused at least. Also the floor of the response max-age in production.
maximumRedirectsnumberdefault 3
Redirects followed for a remote image. Every hop must match remotePatterns too.
fetchTimeoutMsnumberdefault 7000
Timeout for each hop of the remote fetch, in milliseconds.
maxRemoteBytesnumberdefault 26214400 (25MB)
Largest remote body accepted. Anything bigger is a 413.
Server load
maxConcurrencynumberdefault 0
Encodes that run at once. 0 means half the CPUs the server sees, at least one.
Encoding shares one worker pool with file reads and hashing. Raise maxConcurrency and a burst of image requests slows down everything else the server does, which is why the default holds it to half.

Formats And The Platform

Encoding runs on Bun.Image, and its codecs depend on the server's OS. AVIF, HEIC and TIFF need an OS codec that only macOS and Windows have, so a Linux container never emits AVIF.
Codec
macOS · Windows
Linux
Output
image/webp
✓
✓
The default output. Works everywhere.
image/avif
✓
Dropped from formats on Linux; those callers get webp.
Input decoding
JPEG · PNG · GIF · WebP
✓
✓
Read and resized on every platform.
AVIF · TIFF
✓
Passed through untouched on Linux.
✓AvailableNot available
What happens to each input
Input
↳ Result
JPEG · PNG · static GIF
Resized, then converted to the first formats entry the browser accepts.
WebP · AVIF
Resized, but kept in its own format.
Animated GIF · PNG · WebP
Passed through untouched, so the animation survives.
.ico · .icns · .bmp · .jxl · .heic
Passed through untouched.
AVIF · TIFF on Linux
Passed through untouched; there is no codec to read it.
SVG
A 400 unless dangerouslyAllowSVG is on; then it is served as-is.
  • Listing ["image/avif", "image/webp"] is safe. A Linux deployment serves webp to everyone, and a developer's Mac serves both for real.
  • Order matters. The first formats entry the browser's Accept header allows wins, so put image/avif first. A browser that accepts neither gets the source format back, resized.
  • A failed encode still answers. The original bytes are served and nothing is cached, so the next request tries again.

remotePatterns

A remote image is refused until its URL matches an entry in remotePatterns, and the default list is empty. When the optimizer answers 400 for a remote image, check this setting first.
Add each host you serve images from:
apps/myapp/akan.config.ts
FieldHow it matchesExample
protocolExact: http or https"https"
hostnameGlob; * also spans dots"*.example.com"
portExact string; "" means the default port"8443"
pathnameGlob; * is one segment, ** any depth"/articles/**"
searchExact, including the leading ?"?v=2"
  • An omitted field matches anything. { hostname: "cdn.example.com" } admits every path on that host, over http and https.
  • * stops at /, ** does not. A hostname has no /, so *.example.com also matches a.b.example.com.
  • port and search are exact. A pattern can pin a non-standard port or one fixed query, but not a signature that changes per image.
  • Redirects are checked too. Every hop must match, so a CDN that redirects to an unlisted host is refused.

Remote Cache

A remote image is downloaded once, then served from disk until its TTL runs out, so a warm image never reaches its origin. The TTL is the upstream max-age, but never less than minimumCacheTTL.
SourceOriginal re-readServer serves the edit
Remote, production buildOnce the TTL runs outAfter the TTL
Remote, akan startOn every requestImmediately
Local file in public/On every request, compared by mtime and sizeImmediately
  • An unchanged original is not re-encoded. The encoded file is keyed by the upstream ETag, so re-reading the same image costs one download and no encode.
  • Production means a built artifact run with NODE_ENV=production. Under akan start the TTL is skipped, so an upstream edit shows up at once.
  • The browser keeps its own copy. A production response carries a max-age of at least minimumCacheTTL, so a visitor may see the old image until it expires.
  • Each replica has its own cache. It lives in the build artifact directory, so replicas fill it separately and every deploy starts cold.

Cache Hits

Each encoded file is stored under a key made of five parts. The more distinct widths and qualities your pages request, the more rarely reused files the cache splits into.
url
The original's path or URL, from the url parameter.
width
The w parameter, already rounded up to an allowed width.
quality
The q parameter: 75 unless a component passes quality.
output format
The first formats entry the browser accepts, or the source's own format.
source tag
Upstream ETag (or a content hash) for a remote image; mtime and size for a local one.
  • Reuse a few card sizes. Many one-off widths spread across more allowed widths; a handful of repeated sizes lands on the same files.
  • Keep qualities at [75] unless one surface truly needs otherwise. Every extra value multiplies the files, and the client asks only for 75 unless a component passes quality.

Released under the MIT License

Connect your AI to these docs

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js All rights reserved.System managed bybassman