<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 ofwidth, 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
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
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 acrosspylon dev and production.
Allowed widths
The server rejects requests whosew 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 theq parameter.
PYLON_IMAGE_QUALITIES=50,65,75,85,90,95.
Allowed formats
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
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
Pixel-bomb protection
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
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
<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>.