Skip to main content
<Image> renders a plain <img> pointing at Pylon’s built-in optimizer endpoint. The browser fetches an AVIF or WebP (or JPEG, depending on Accept) sized for the user’s viewport, served from disk cache on every subsequent request. The pipeline is pure Rust: image for decode, fast_image_resize (SIMD) for the actual resize, and ravif / rav1e (AVIF), libwebp, and mozjpeg for encode. No Sharp install, no libvips on the host, no Node child-process. It all lives in the Pylon binary.

What it does

For a single <Image src="/hero.jpg" width={1200} height={800} />:
  • Renders <img srcset> with multiple width candidates (default: 1x and 2x of width, capped at 3840px).
  • Each candidate points at /_pylon/image?src=/hero.jpg&w=<width>&q=<quality>.
  • Browser picks the smallest candidate that satisfies the viewport DPR.
  • Server decodes once, resizes with SIMD (Lanczos3), encodes as AVIF, WebP, or JPEG depending on Accept.
  • Result is hashed by (src, width, quality, format) and cached at .pylon/.cache/images/<hash>.<ext>. Cache hits skip decode + encode entirely — just a file serve.
  • Response carries Cache-Control: public, max-age=31536000, immutable.

API

Examples

Hero image (above the fold)

priority skips loading="lazy" and sets fetchPriority="high" so the browser prioritizes it during the initial paint.

Responsive grid

The sizes attribute is the most important knob — it tells the browser how wide the image will actually render at each breakpoint, so it picks the smallest viable srcset candidate. Without it, the browser assumes 100vw and downloads a 4K image for a 200px thumbnail.

Custom srcset

Useful for avatar grids where you know the exact DPR multipliers you care about.

Safety & configuration

The optimizer is opinionated about what it’ll process. Every knob has a safe default, and you tune them via env. The same defaults apply across pylon dev and production.

Allowed widths

The server rejects requests whose w isn’t in its width set. This prevents cache-fill DoS where an attacker requests thousands of slightly-different widths to balloon your disk.
<Image> generates srcset URLs only with widths from this combined set, so default-config usage just works. If you customize the env, also pass widths={[...]} on each <Image> to keep srcset URLs in range.

Allowed qualities

Same defense, applied to the q parameter.
If you need finer-grained quality control, append it: PYLON_IMAGE_QUALITIES=50,65,75,85,90,95.

Allowed formats

Locks the optimizer to a specific output set. The server picks the best match for the request’s Accept header from this list. <Image> lets the server pick by default; pass format= in your own URLs if you want explicit control.

Remote source allowlist

Each entry is host or host/pathPrefix: Without this env, any src starting with http:// or https:// returns 400. This prevents SSRF — random visitors can’t make your server fetch arbitrary internal URLs.

Source size cap

Applies to both local files and remote fetches. Remote responses that exceed it are rejected mid-stream.

Pixel-bomb protection

A tiny PNG can declare a 100000×100000 canvas. Decoding that would allocate 40GB of RGBA. Pylon reads the image header BEFORE decoding and rejects any source whose declared width × height exceeds this cap. The default (40M px, ~160MB peak decode) is tuned so a burst of concurrent requests can’t OOM a small machine; operators serving genuinely huge sources raise it explicitly.

Fetch timeout

Applies to remote source fetches.

SVG handling

SVG is always rejected, both as input and output. SVG can carry inline <script> and CSS — letting users serve arbitrary SVG through an optimization endpoint would be an XSS vector. There’s no dangerouslyAllowSVG toggle by design: if you need SVGs, render them with plain <img src="/icon.svg"> (not through <Image>), serve them with Content-Security-Policy: script-src 'none', and you’re fine. For embedded <Image unoptimized> use, see below.

Local file source

src starting with / resolves under the frontend dir (PYLON_FRONTEND_DIR or <app>/web/dist). Path traversal is hard-rejected — canonicalize + prefix-check, so .. segments and symlinks pointing outside the dir return 400.

Bypassing the optimizer per-image

Renders a plain <img src="/icon.svg"> with no srcset and no optimizer round-trip. Useful for SVGs, animated GIFs, or images where the source is already pre-sized.

Format selection

AVIF is served automatically to browsers that advertise image/avif (Chrome, Firefox, Safari 16+) — it’s the smallest of the three formats. The tradeoff is a slower cold encode (rav1e at speed 8), which the on-disk cache amortizes to a one-time cost per (src, width, quality) tuple. Browsers that don’t advertise AVIF fall back to WebP, still ~30% smaller than mozjpeg at the same perceptual quality; unknown clients (curl, bots that send no Accept) also get WebP.

Performance

Benchmarks on a 2400×1600 source JPEG (~250KB) on Apple Silicon: Tail latency is bounded by the encoder; cache hits are bounded by your disk. For high-traffic surfaces, put a CDN in front and Pylon never re-processes anything — the immutable URL takes care of the rest.

Env reference

The cache lives at <cwd>/.pylon/.cache/images/. Delete it to force regeneration. There’s no LRU eviction yet — content-addressed entries are append-only, so the cache grows with the variety of (src, width, quality, format) tuples you serve. For production this is rarely a problem; the entire long-tail of a site’s images is usually < 1GB. A pylon cache clean command will arrive when it actually matters.

Differences from Next.js’s <Image>

placeholder="blur" is on the roadmap. SVG support is deliberately off the roadmap — render SVG with a plain <img> tag, not through <Image>.